Erros como valores
Em Python, uma função que falha lança uma exceção — um segundo caminho de retorno oculto que interrompe o fluxo normal até que algo a capture. Em Glide, uma função que falha retorna um valor especial que o chamador precisa examinar. Os modos de falha vivem na assinatura do tipo. Nada fica escondido.
Isso parece mais trabalhoso, e de início é — mas a recompensa é que você não consegue ignorar um erro por acidente, e todo lugar onde as coisas podem dar errado é visível direto no código-fonte.
Os dois tipos em formato de falha
Existem dois tipos que codificam "a coisa pode não estar lá":
- `!T` — leia "bang T," significando "um T, ou uma mensagem de erro". Construído com
ok(value)ouerr("message"). - `?T` — leia "maybe T," significando "um T, ou nada". Construído com
some(value)ounone().
Use !T quando algo falhou por um motivo que vale explicar. Use ?T quando simplesmente não há valor a retornar, e você não precisa de um motivo.
fn parse_int(s: string) -> !i32 {
if s.eq("") { return err("empty input"); }
if !all_digits(s) { return err("not a number"); }
return ok(/* o valor analisado */ 42);
}
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();
}
Lendo o valor de dentro
Os dois tipos têm dois campos que você pode inspecionar: uma flag e um payload.
Para !T:
.okétruequando a operação teve sucesso.valcontém o valor (só faz sentido quando.oké true).errcontém a mensagem (só faz sentido quando.oké false)
let r: !i32 = parse_int("42");
if r.ok {
println!("parsed:", r.val);
} else {
println!("error:", r.err);
}
Para ?T:
.hasétruequando há um valor.valo contém
let v: ?string = first_vowel("hello");
if v.has {
println!("got:", v.val);
} else {
println!("no vowel");
}
O operador ?
Verificar .ok manualmente após cada chamada que pode falhar fica tedioso rápido:
fn pipeline(s: string) -> !i32 {
let a: !i32 = parse_int(s);
if !a.ok { return err(a.err); } // interrompe
let b: !i32 = parse_int(/* outra coisa */);
if !b.ok { return err(b.err); } // interrompe de novo
return ok(a.val + b.val);
}
O operador ? colapsa esse padrão em um único caractere. Coloque-o logo após um valor !T:
fn pipeline(s: string) -> !i32 {
let a: i32 = parse_int(s)?; // err → retorna err; ok → desempacota para a
let b: i32 = parse_int(s)?;
return ok(a + b);
}
O que ? faz, mecanicamente:
- Se o
.okdo valor forfalse, retorna esse erro da função que o envolve. - Caso contrário, avalia para o
.val.
Ele só funciona dentro de uma função que, ela mesma, retorna !T (ou ?T, que tem semântica análoga para none).
O operador ??: valores padrão
Às vezes você não se importa com o erro — você só quer um valor de fallback. ?? é o operador para isso:
let port: i32 = parse_int(env_get("PORT")) ?? 8080;
Se parse_int tiver sucesso, port recebe seu valor. Se falhar, port recebe 8080. Sem if, sem match, sem retorno antecipado.
?? também funciona com ?T:
let first: string = first_vowel(input) ?? "no vowels";
Construindo os valores
Os construtores são funções em letras minúsculas:
return ok(42);
return err("bad input");
return some("hi");
return none();
Todas as quatro são chamadas em minúsculas. none() não recebe argumento porque não há nada a envolver; as outras três recebem a coisa que envolvem.
defer e defer_err
Quando uma função abre um recurso (um arquivo, uma conexão, uma alocação) ela normalmente precisa fechá-lo antes de retornar — quer a função tenha tido sucesso ou não. Glide tem duas palavras-chave para isso.
`defer` agenda um código para ser executado quando a função que o envolve sair, não importa como (retorno normal, propagação por ?, return antecipado). É assim que você garante a limpeza:
fn process(path: string) -> !i32 {
let f: *File = open(path)?;
defer f.close(); // executa quando esta função retornar, ponto final
let line: string = f.read_line()?; // se isso falhar, f ainda será fechado
return ok(line.len());
}
`defer_err` é a mesma ideia, mas só é acionado no caminho de erro — útil para ações compensatórias:
fn create_user(name: string) -> !User {
let id: i64 = db_insert(name)?;
defer_err db_delete(id); // desfaz APENAS se algo abaixo falhar
send_welcome_email(name)?; // se isso falhar, o rollback é executado
return ok(User { id: id, name: name });
}
Se send_welcome_email falhar, defer_err executa e a inserção parcial é desfeita. Se tudo der certo, defer_err é ignorado — o usuário permanece.
Comparado ao try/except do Python
Considere este Python:
def get_config(path):
try:
with open(path) as f:
data = json.load(f)
return data["port"]
except FileNotFoundError:
return 8080
except json.JSONDecodeError as e:
raise ValueError(f"bad JSON: {e}")
Em Glide, a mesma lógica fica assim:
import stdlib::fs::*;
import stdlib::json::*;
fn get_config(path: string) -> !i32 {
let raw: string = fs_read_or_default(path, ""); // arquivo ausente → ""
if raw.eq("") { return ok(8080); } // porta padrão
let cfg: *JsonValue = JsonValue::parse(raw);
let port: i32 = cfg.get_int("port")?; // chave ausente/não inteira → propaga
return ok(port);
}
Observe:
fs_read_or_defaultcolapsa o caso de "arquivo ausente" para um fallback inline — semtry/except.cfg.get_int("port")retorna!i32, então o?propaga um erro de "chave inexistente" / "não é um inteiro" — o chamador o vê como!i32com essa mensagem.- O tipo de retorno
!i32anuncia que esta função pode falhar. Quem a chama sabe imediatamente.
Quando erros são verdadeiramente irrecuperáveis
Para estados genuinamente impossíveis ("o array está vazio aqui, o que meus invariantes proíbem"), use panic! ou assert! — eles imprimem uma mensagem com a localização no código-fonte e abortam o processo:
let v: *Vector<i32> = Vector::new();
v.push_all!(1, 2, 3);
if v.len() == 0 {
panic!("this can't happen — I just pushed three elements");
}
Mas reserve isso para casos genuinamente "impossíveis". Qualquer coisa que um usuário ou a rede possa desencadear deve ser !T e propagar normalmente.
Recapitulando
Para onde ir agora
Você já sabe escrever funções que falham com segurança. O próximo capítulo — Modelo de memória — explica a outra metade da história de segurança do Glide: como a linguagem rastreia quem possui cada pedaço de memória sem um coletor de lixo, e sem anotações de tempo de vida como as do Rust.