Ir para o conteúdo

Structs, variants e enums ​

struct: um tipo produto ​

talor
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 name

Os 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.

talor
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.

talor
enum Errno { Interrupted = 4, NotFound = 2, Denied = 13 }
enum Color { Red, Green, Blue }         // 0, 1, 2

Um 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:

talor
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:

talor
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.

talor
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:

talor
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.

Talor v0.1.0 - Publicado sob a licença MIT OR Apache-2.0.