Ir para o conteúdo

Tour de cinco minutos ​

Cada passo é uma ideia completa, e todas as amostras são Core v0, o subconjunto em que o compilador auto-hospedado é escrito.

1. Nomes, e o que eles podem fazer ​

talor
const LIMIT: i64 = 10;          // module level, computed at compile time

fn fibonacci(n: i64): i64 {
    if n < 2 { return n; }
    let prev: i64 = 0;          // a local the body writes is mutable
    let curr: i64 = 1;
    let i: i64 = 2;
    while i <= n {
        let next = prev + curr; // never written, so never mutable
        prev = curr;
        curr = next;
        i += 1;
    }
    curr                        // the tail expression is the value
}

if, while e blocos não precisam de parênteses em volta da condição, mas as chaves são obrigatórias em todo corpo. O valor de uma função é a última expressão dela, sem ;.

2. Texto ​

talor
fn describe(n: i64): string {
    let size = if n < 10 { "small" } else if n < 100 { "medium" } else { "large" };
    let parity = { let r = n % 2; if r == 0 { "even" } else { "odd" } };
    `${n} is ${size} and ${parity}`
}

if e um bloco são expressões. Um template literal é o único mecanismo de formatação: cada ${...} carrega uma expressão, e as partes são concatenadas num string.

3. Um valor, uma entre várias formas ​

talor
use std.math.{PI};

variant Shape {
    Circle(f64),
    Rect { w: f64, h: f64 },
    Dot,
}

fn area(s: Shape): f64 {
    match s {
        Shape.Circle(r) => PI * r.powi(2),
        Shape.Rect { w, h } => w * h,
        Shape.Dot => 0.0,
    }
}

Um variant é uma união marcada: um valor é exatamente um caso, cada caso carrega os próprios dados, e a única forma de lê-lo é um match, que precisa ser exaustivo. O acesso a membro é um ponto, sempre: Shape.Circle(1.0). Não existe ::.

4. Ausência e falha são valores ​

talor
fn parse_count(s: string): Result<i64, string> {
    match s.to_i64() {
        Some(n) => Ok(n * 2),
        None => Err(`not a number: ${s}`),
    }
}

fn double_or_fail(s: string): Result<i64, string> {
    let n = parse_count(s)?;    // returns the Err from here on Err
    Ok(n)
}

Não existe null e não existe exceção: ausência é Option<T> (ou T?), falha é Result<T, E>, e ? propaga.

5. O que uma função faz com os seus valores ​

talor
use std.math.{PI};

// Shape and area, as step 3 wrote them.
variant Shape {
    Circle(f64),
    Rect { w: f64, h: f64 },
    Dot,
}

fn area(s: Shape): f64 {
    match s {
        Shape.Circle(r) => PI * r.powi(2),
        Shape.Rect { w, h } => w * h,
        Shape.Dot => 0.0,
    }
}

fn append(dst: Array<i64>, src: Array<i64>) {
    for v in src {
        dst.push(v);
    }
}

fn main(): i32 {
    let shapes: Array<Shape> = [Shape.Circle(2.0), Shape.Rect { w: 3.0, h: 4.0 }];
    let areas: Array<i64> = [];
    for s in shapes {
        areas.push(area(s) as i64);
    }
    append(areas, [100, 200]);
    println(`areas: ${areas.len()}`);
    0
}

Parâmetros passam por borrow por padrão, e o compilador lê o corpo para o resto: append insere em dst, então dst é edits e o argumento precisa ser um lugar que possa ser escrito; uma função que fica com o argumento é takes, e usar o valor depois disso é um erro que nomeia a chamada. Nada é escrito no call site, nunca. Uma clause depois da assinatura (edits dst, takes src) declara esse efeito quando o autor quer dizê-lo em voz alta; ela é opcional, e uma função que não diz nada é verificada exatamente com o mesmo rigor.

6. Tipos próprios ​

talor
use std.list.{filter};

struct Account { owner: string, cents: i64 }

impl Account {
    fn deposit(self, amount: i64) { self.cents += amount; }
    fn label(self): string { `${self.owner}: ${self.cents} cents` }
}

trait Describe { fn describe(self): string; }
impl Describe for Account { fn describe(self): string { self.label() } }

fn largest<T: Describe>(items: Array<T>, size: fn(T): i64): string {
    let best = 0;
    let text = "";
    for it in items {
        if size(it) > best { best = size(it); text = it.describe(); }
    }
    text
}

fn main(): i32 {
    let a = Account { owner: "ana", cents: 500 };
    a.deposit(250);
    let all = [a, Account { owner: "bia", cents: 900 }];
    let rich = filter(all, (x) => x.cents > 600);
    println(largest(rich, (x) => x.cents));
    0
}

Métodos ficam num bloco impl, nunca dentro da struct, e deposit escreve em self, o que basta para que a chamada precise de um valor que possa ser escrito. Um trait é implementado pelo nome, um parâmetro generic declara os traits de que precisa (T: Describe), e uma closure como (x) => x.cents é um valor cujo tipo de parâmetro a chamada infere.

Para onde ir agora ​

  • Aprender cobre cada um destes pontos por inteiro.
  • Os capítulos detalhados estão em português, com aviso nas páginas que ainda não foram traduzidas: Aprender.

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