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