Funções e traits
A função
fn add(a: i64, b: i64): i64 { a + b } // tail expression is the value
fn greet(name: string) { println(`hi ${name}`); } // no return type: the body's, here ()
fn first<T>(xs: Array<T>): Option<T> { panic("the body") }
fn show<T: Display>(x: T): string { x.fmt() }
pub fn exported(): i64 { return 1; }- O tipo de retorno vem depois de
:. Omitido, ele é o tipo do corpo:fn total(self) { self.a + self.b }devolvei64, e um corpo que termina em um statement devolve(). Um corpo que o compilador não consegue tipar sozinho, um que usa?por exemplo, é recusado com uma mensagem que diz para escrever o tipo. return expr;sai mais cedo. A última expressão do corpo, sem;, é o valor de retorno.- Parâmetros passam por borrow por padrão; a clause depois da assinatura diz o resto.
- Parâmetros generic aparecem depois do nome:
<T, U: Bound>. Um corpo generic é checado uma vez, com seus parâmetros de tipo opacos, mesmo quando nada o chama, então só o que os bounds permitem é válido em umT.
Argumentos default
Um parâmetro no fim da lista pode ter um valor default, escrito depois do tipo. Uma chamada que omite o argumento recebe o default, preenchido no call site:
fn greet(name: string, greeting: string = "hello"): string { `${greeting}, ${name}` }
fn main(): i32 {
println(greet("ana"));
println(greet("bia", "hi"));
0
}- Só os parâmetros do fim têm um: depois de um parâmetro com default, todo parâmetro tem um.
- Um default é um literal ou o nome de uma
const, nunca uma chamada ou outra expressão. - Não existem argumentos nomeados: um argumento é colocado pela posição, e omitir um omite todos os que vêm depois dele.
- Uma função usada como valor mantém a assinatura completa, então uma chamada através do valor passa todos os argumentos.
Métodos: impl
impl é o único mecanismo para métodos; eles nunca são escritos dentro do corpo da struct.
impl Point {
fn of(x: f64, y: f64): Point { Point { x, y } } // no self: static, Point.of(..)
fn norm(self): f64 { sqrt(self.x * self.x + self.y * self.y) }
fn scale(self, k: f64) { self.x *= k; self.y *= k; }
fn into_pair(self): (f64, f64) { (self.x, self.y) }
}
impl<T> Wrapper<T> {
fn get(self): T { self.value }
}O receiver é sempre self, e o efeito de um método sobre ele segue a mesma regra de qualquer outro parâmetro: o compilador o infere do corpo, e uma clause (edits self, takes self) o declara quando o autor o quer na assinatura.
new não é keyword: um construtor é um método estático comum, então dê a ele o nome do que ele faz (of, empty, with_capacity). O único new que a linguagem define é Box.new.
Traits
Traits são nominais: um tipo implementa uma trait só por um bloco impl Trait for Type. Ter os métodos certos não basta.
pub trait Display {
fn fmt(self): string;
}
impl Display for Point {
fn fmt(self): string { `(${self.x}, ${self.y})` }
}
fn show<T: Display>(x: T): string { x.fmt() }Chamadas através de um bound são monomorfizadas: dispatch estático, uma cópia por tipo usado. Uma trait pode receber argumentos de tipo, e o impl diz quais - é assim que uma trait nomeia um tipo que ela não possui:
trait Peek<T> { fn peek(self): T; }
impl Peek<i64> for Cell { fn peek(self): i64 { self.n } }Uma trait ainda não tem corpos default de método nem tipos associados: toda implementação escreve todos os métodos.
Um método de trait não carrega edits nem takes próprios: o efeito de cada implementação é lido do corpo dela, e uma chamada através de um bound usa a implementação que escolheu. Onde a trait é usada como dyn, as implementações precisam concordar sobre o que fazem com self e com cada parâmetro, porque uma tabela de métodos tem uma calling convention por método; a recusa nomeia as duas implementações que diferem.
dyn Trait: um tipo para toda implementação
trait Speak { fn say(self): string; }
struct Dog { name: string }
struct Cat { lives: i64 }
impl Speak for Dog { fn say(self): string { `${self.name} barks` } }
impl Speak for Cat { fn say(self): string { `a cat with ${self.lives} lives` } }
fn hear(s: dyn Speak): string { s.say() }
fn main(): i32 {
let d: dyn Speak = dyn Dog { name: "Rex" }; // `dyn` makes one, like `shared` does
let c: dyn Speak = dyn Cat { lives: 9 };
println(hear(d));
println(hear(c));
0
}Um valor dyn Trait ocupa duas palavras de máquina: o valor no heap e a tabela dos métodos da trait para o tipo dele, escolhida em tempo de execução. A trait é inferida do contexto onde existe um. Uma trait cujo método recebe ou devolve self por valor não tem forma dyn.
Closures
let twice = (a: i64) => a * 2;
let sum = (a: i64, b: i64) => { let s = a + b; s };Uma closure é um valor de função que carrega as variáveis que menciona. O tipo dela é fn(A): R, e ela vai aonde um valor vai: um parâmetro, um valor de retorno, um campo, um elemento de um array.
use std.list.{map, filter, fold};
fn make_adder(n: i64): fn(i64): i64 {
(x: i64) => x + n
}
fn apply_twice(f: fn(i64): i64, x: i64): i64 { f(f(x)) }
fn main(): i32 {
let add5 = make_adder(5);
println(`add5(10) is ${add5(10)}`);
println(`applied twice, ${apply_twice(add5, 0)}`);
let xs: Array<i64> = [5, 3, 8, 1, 9, 2];
let doubled = map(xs, (x) => x * 2);
let big = filter(doubled, (x) => x > 6);
let total = fold(big, 0, (a, b) => a + b);
println(`doubled, over six, summed: ${total}`);
0
}make_adder devolve uma closure que capturou n, e add5 mantém esse n depois que make_adder retornou. Uma chamada a uma função generic infere os tipos dos parâmetros de uma closure a partir dos outros argumentos, então eles só são escritos quando nada mais os diz. Closures tem o resto: a captura por move, a closure que só pode ser chamada uma vez, a captura de uma view e o compartilhamento de uma closure entre tasks.
Um limite: uma função que escreve, fica com ou devolve um dos seus parâmetros, ou uma parte de um, não pode ser usada como valor. O tipo fn(A): R não carrega clause nem vínculo entre o resultado e um argumento, então uma chamada através do valor não teria como expressar nenhum dos dois.
unsafe
unsafe marca uma declaração cujo contrato o compilador não consegue checar:
unsafe extern fn talor_env_get(name: string): string; // C ABI, no body
unsafe fn write_raw(fd: i64, p: i64, n: i64): i64 { panic("the body") }Todo extern fn é uma delas, então ele é escrito unsafe extern fn. Chamar qualquer uma das duas só é permitido de dentro de outra unsafe fn ou de um bloco unsafe { ... }. A palavra aparece onde o perigo está e onde ele é assumido, e em nenhum outro lugar.
Uma trait pode ser uma unsafe trait: implementá-la é uma promessa que o compilador não consegue checar, então ela só é implementada por um unsafe impl.