Funções e controle de fluxo
Um programa é funções chamando funções. Este capítulo trata dessa superfície: como defini-las, como ramificar e iterar, e um recurso que surpreende quem vem do Python — a maioria das coisas em Glide são expressões, incluindo blocos.
Funções
A forma geral:
fn add(a: i32, b: i32) -> i32 {
return a + b;
}
Leia da esquerda para a direita:
fn— declara uma função.add— seu nome.(a: i32, b: i32)— seus parâmetros. Todo parâmetro tem um tipo explícito. Não há*argsnem**kwargs— o que você declara é o que os chamadores devem passar.-> i32— o tipo de retorno. Uma função que não retorna nada omite a seta:
fn shout(msg: string) {
println!(msg.to_upper());
}
{ ... }— o corpo, um bloco. Instruções terminam com;.return a + b;— retorno explícito. Isso não é negociável em Glide; não inferimos valores de retorno a partir de expressões finais como o Rust faz.
Chamando uma função
Exatamente como você esperaria:
fn main() -> i32 {
let s: i32 = add(3, 4);
println!(s); // 7
return 0;
}
Passando valores grandes
Escalares (i32, bool, f64...) são passados por valor — o receptor recebe uma cópia. Objetos na heap (*Vector<T>, *HashMap<V>, ...) são passados por ponteiro — tanto o chamador quanto o receptor enxergam o mesmo valor. Passar um ponteiro é barato (é apenas um endereço).
Se você quiser que o receptor leia um valor da heap sem assumir a posse dele, você passa um empréstimo:
fn print_first(v: &*Vector<i32>) {
println!(v.get(0));
}
fn main() -> i32 {
let v: *Vector<i32> = Vector::new();
v.push_all!(1, 2, 3);
print_first(&v);
println!(v.len()); // ainda é nosso — empréstimos não consomem
return 0;
}
O & significa "empréstimo" — como emprestar um livro. As regras de empréstimo têm um capítulo inteiro (Modelo de memória); por ora, observe apenas que & é "dar acesso de leitura" e &mut é "dar acesso de escrita".
if é uma expressão
A forma familiar funciona:
if x > 0 {
println!("positive");
} else if x < 0 {
println!("negative");
} else {
println!("zero");
}
Mas — e este é o truque principal — todo if é também um valor. Você pode colocá-lo no lado direito de um =:
let kind: string = if x > 0 { "positive" }
else if x < 0 { "negative" }
else { "zero" };
Toda a cadeia if/else é avaliada como uma das três strings, que fica associada a kind. Na posição de expressão, cada ramo é um valor único sem `return` e sem `;` no final — o valor simples é o resultado do ramo. (else é obrigatório aqui: uma expressão deve sempre produzir algo.)
match
match é um switch sobre a forma de um valor — uma variante de enum, ou o some/none de um ?T. Cada braço tem a forma padrão => resultado. match é onde os enums brilham:
enum HttpMethod {
Get,
Post,
Put,
Delete,
}
fn allow_body(m: HttpMethod) -> bool {
return match m {
HttpMethod::Get => false,
HttpMethod::Post => true,
HttpMethod::Put => true,
HttpMethod::Delete => false,
};
}
- Cada braço tem a forma
Padrão => valor,(separados por vírgula, valor simples — mesma regra de expressão que oif). Se um braço precisar de várias instruções, use um bloco que termina emreturn:HttpMethod::Get => { do_thing(); return false; }. _é o curinga — captura qualquer coisa que os braços anteriores não tenham tratado.- O compilador verifica se você cobriu todas as variantes do enum. Se você adicionar um
HttpMethod::Patch, receberá um erronon-exhaustive-matchaté tratá-lo — o que captura uma classe real de bugs.
Laços
Três formas de laço.
`while` executa enquanto a condição for verdadeira:
let mut i: i32 = 0;
while i < 10 {
println!(i);
i = i + 1;
}
`for` é o clássico laço de três partes no estilo C (init; condição; passo):
for let j: i32 = 0; j < 10; j = j + 1 {
println!(j);
}
`for ... in` percorre qualquer iterável (Vector, intervalo, HashMap, qualquer coisa que implemente o trait Iterator):
let v: *Vector<i32> = Vector::new();
v.push_all!(10, 20, 30);
for x in v {
println!(x);
}
break sai do laço mais próximo; continue pula para a próxima iteração.
Intervalos
Uma expressão de intervalo é inicio..fim (meio-aberto, fim excluído) ou inicio..=fim (fechado, fim incluído):
for i in 0..5 { println!(i); } // 0, 1, 2, 3, 4
for i in 0..=5 { println!(i); } // 0, 1, 2, 3, 4, 5
for i in 1..=10 { println!(i * i); }
Intervalos são apenas valores do tipo Range<i32> (ou Range<i64>). Você pode coletá-los, filtrá-los — tudo que existe em stdlib::iter funciona com eles.
Blocos como valores
Um bloco simples { ... } executa suas instruções e produz um valor via return:
let result: i32 = {
let a: i32 = expensive();
let b: i32 = also_expensive();
return a + b;
};
É a mesma mecânica que if e match usam. O ponto de um bloco simples é delimitar bindings temporários: a e b são descartados na chave de fechamento, mesmo que sua soma continue viva em result. Você recorreria a isso quando um cálculo precisa de variáveis intermediárias que nenhuma outra parte da função deve ver.
Um primeiro olhar em ? para propagação de erros
Quando uma função retorna !T (lembre: "um T ou um erro"), você geralmente quer encerrar no caso de erros. O operador ? faz isso em um único caractere:
fn parse_int(s: string) -> !i32 {
if s.eq("") { return err("empty"); }
return ok(42); // fingindo que parseamos
}
fn double(s: string) -> !i32 {
let n: i32 = parse_int(s)?; // se err → retorna err; se ok → desempacota para n
return ok(n * 2);
}
? é a abreviação de "se isso retornou um erro, retorne-o desta função também; caso contrário, extraia o valor". Funciona apenas dentro de uma função que ela própria retorna !T ou ?T. Dedicaremos um capítulo inteiro a isso — veja Erros como valores.
Um exemplo completo
Vamos combinar alguns desses conceitos. Aqui está uma função que retorna a primeira vogal de uma string, ou none se não houver nenhuma:
fn is_vowel(c: i32) -> bool {
return c == 97 || c == 101 || c == 105 || c == 111 || c == 117
|| c == 65 || c == 69 || c == 73 || c == 79 || c == 85;
}
fn first_vowel(s: string) -> ?string {
for let i: i32 = 0; i < s.len(); i = i + 1 {
if is_vowel(s.at(i).to_int()) {
return some(s.substring(i, i + 1));
}
}
return none();
}
fn main() -> i32 {
let r: ?string = first_vowel("rhythm and blues");
if r.has {
println!("got:", r.val);
} else {
println!("no vowel");
}
return 0;
}
Três coisas a observar:
s.at(i)retorna um caractere;.to_int()o amplia para umi32para que possamos compará-lo a códigos ASCII.is_vowelretorna umboole é chamada como qualquer outra função.first_vowelretorna?string— às vezes tem um valor, às vezes énone..hase.valsão como você lê esse resultado.
Para onde ir agora
Você já tem funções, ramificações, laços e uma prévia de ?. O próximo capítulo — Erros como valores — é o grande: como Glide lida com falhas sem exceções.