Structs, variants e enums
struct: um tipo produto
struct Point { x: f64, y: f64 }
struct Wrapper<T> { value: T, count: i64 }
let p = Point { x: 1.0, y: 2.0 };
let q = Point { x, y }; // shorthand when a variable has the field's nameOs campos de uma struct são separados por ,; uma vírgula no final é aceita. Uma struct é um tipo valor com layout fixo. Todo literal de struct nomeia o seu tipo; não existem tipos de objeto anônimos. Duas structs com os mesmos campos são tipos diferentes.
variant: uma tagged union
Uma variant é um tipo soma: um valor é exatamente um dos casos listados, nunca dois, e cada caso carrega os seus próprios dados.
variant Shape {
Circle(f64), // positional payload
Rect { w: f64, h: f64 }, // named payload
Empty, // no payload
}
variant Tree<T> { Leaf(T), Node(Box<Tree<T>>, Box<Tree<T>>) }O layout é { i32 tag, payload }, em que o payload é um bloco do tamanho do maior caso, do jeito que uma union de C compartilha os seus membros. O que C não tem é a tag: o compilador a escreve na construção e a confere antes de toda leitura. Não existe sintaxe que leia um payload sem essa conferência - um caso só é lido por meio de match.
Um caso é construído pelo nome através do seu tipo, com o ponto: Shape.Circle(1.0). Não existe s.Circle em um valor, e não existe :: em lugar nenhum.
Option<T> e Result<T, E> são variants, e é por isso que Some(p), None, Ok(p) e Err(e) funcionam como padrões em toda parte.
enum: um conjunto numerado
Um enum é a outra metade: nenhum caso carrega dados, e cada um tem um valor inteiro. Essa é a construção de C que a palavra nomeia, e as duas não se sobrepõem: uma variant precisa de pelo menos um caso que carregue dados, um enum recusa qualquer um, e cada recusa nomeia a outra palavra.
enum Errno { Interrupted = 4, NotFound = 2, Denied = 13 }
enum Color { Red, Green, Blue } // 0, 1, 2Um caso sem valor recebe um a mais que o anterior; o primeiro recebe zero. Dois casos não podem ter o mesmo valor. O valor é a tag, então um enum é um i32 e pode ser lido nos dois sentidos:
let n = Errno.Denied as i64; // 13
let name = match Errno.of(2) {
Some(Errno.NotFound) => "not found",
Some(_) => "another case",
None => "unknown", // no case has that value
};E.of(n) responde Option<E>, nunca um cast: um número qualquer não é um caso, e a conferência é o que mantém o tipo honesto.
Recursão
Um tipo recursivo não precisa de palavra nenhuma:
variant Tree { Leaf(i64), Node(Tree, Tree) }O compilador põe automaticamente a edge que fecha o ciclo atrás de uma indireção, então o programador escreve o valor e nunca vê o box. Um ciclo mútuo recebe o mesmo tratamento, e um anel de três tipos ou mais também: o membro que fecha o ciclo carrega a indireção.
variant Expr { Num(i64), Neg(Stmt) }
variant Stmt { Eval(Expr), Skip }
fn depth(e: Expr): i64 {
match e {
Expr.Num(_) => 1,
Expr.Neg(s) => match s { Stmt.Eval(x) => 1 + depth(x), Stmt.Skip => 1 },
}
}
fn main(): i32 {
let e = Expr.Neg(Stmt.Eval(Expr.Num(3)));
println(`${depth(e)}`);
0
}Um programador que quer a indireção em outro lugar escreve Box<T> ali.
Igualdade
== e != comparam um valor parte por parte, na ordem de declaração, e param na primeira parte que difere. O walk segue uma parte até o heap: uma string compara os seus caracteres, um Array os seus elementos, um Box aquilo para o qual aponta - e é assim que um tipo recursivo chega a ter igualdade.
Um tipo cujo valor não é a sua representação diz isso, com um par:
struct Pair { a: i64, b: i64 } // a set of two numbers, so order is not value
impl Pair {
fn eq(self, other: Pair): bool {
(self.a == other.a && self.b == other.b) || (self.a == other.b && self.b == other.a)
}
fn hash(self): u64 { panic("the body") }
}Os dois vêm em par, e um tipo que declara um sem o outro é recusado: um container que compara por uma regra e calcula o hash por outra guarda uma entrada sob a primeira e a procura sob a segunda. Só == e != - não existe ordem em um agregado.
Uma closure não tem igualdade, nem um handle extern type, nem qualquer valor que contenha um deles; comparar um deles é recusado.
O que todo tipo ganha
Uma struct, uma variant ou um enum que o programa declara tem .clone(), ==, hash(x) e .debug() sem nada escrito: .debug() responde o valor como texto, P { x: 1, y: 2 } ou Shape.Rect(1, 2). Um tipo que contém algo que não aceita clone nem comparação não tem a operação que precisa disso.