Capítulo 30 21 min de leitura

Sistema e ferramentas

A biblioteca padrão do Glide expõe alguns módulos que ficam próximos ao runtime e ao próprio compilador: tratamento tipado de sinais do SO que se integra ao select!, captura de backtrace em tempo de execução, a superfície AST de meta para bibliotecas de macros, e os códigos de lint que o compilador emite em tempo de build. Cada um é pequeno e focado; esta página documenta todos os itens públicos.

Import

import stdlib::signal::*;      // enum Signal + signal_chan / raise / default / ignore
import stdlib::backtrace::*;   // stack_trace / stack_trace_skip
import stdlib::meta::*;        // nós AST Expr/Stmt/Type/... + construtores (para @proc_macro)
import stdlib::lint::*;        // códigos LINT_* + lint_code()

Cada módulo é independente — importe apenas os que usar.

Módulo Superfície pública Resumo
stdlib::signal 1 enum (3 métodos) + 4 funções livres + 1 helper interno Sinais do SO entregues como valores chan<Signal>.
stdlib::backtrace 2 funções Percorre a pilha de chamadas ativa em um *Vector<string>.
stdlib::meta re-export de bootstrap::ast (10+ structs, 100+ constantes, 50+ construtores) A superfície AST em tempo de compilação para autores de @proc_macro.
stdlib::lint 8 constantes + 1 função Nomes para os códigos de aviso embutidos do compilador + um helper para lint customizado.

Sinais

stdlib::signal entrega sinais do SO como valores em um chan<Signal>, de forma que o tratamento de sinais se compõe com o restante da sua concorrência via select!/recv(), em vez de rodar dentro de um handler async-signal-unsafe. No POSIX, a implementação usa o clássico truque do self-pipe: um handler sigaction escreve o signum num pipe, uma thread leitora o lê e empurra um Signal para o canal. No Windows, conecta-se ao SetConsoleCtrlHandler (Ctrl+C → SIGINT, Ctrl+Break → SIGTERM, fechar/logoff/shutdown → SIGHUP).

Superfície pública

Item Assinatura Descrição
enum Signal 8 variantes nomeadas + Other(i32) O valor do sinal carregado no canal.
Signal::to_int fn to_int(self: Signal) -> i32 Signum POSIX.
Signal::from_int fn from_int(n: i32) -> Signal Constrói a partir de um signum bruto (total).
Signal::name fn name(self: Signal) -> string Nome no estilo POSIX.
signal_chan fn signal_chan(s: Signal) -> chan<Signal> Inscreve-se; retorna o canal de entrega.
signal_default fn signal_default(s: Signal) -> ! Restaura o handler padrão do SO.
signal_ignore fn signal_ignore(s: Signal) -> ! Descarta o sinal inteiramente.
signal_raise fn signal_raise(s: Signal) -> ! Lança o sinal contra este processo.
_signal_push fn _signal_push(c: chan<Signal>, n: i32) Interno — empurra from_int(n) para um canal.

enum Signal

Os oito sinais POSIX com nomes consagrados, mais uma válvula de escape Other(i32) para qualquer signum fora do conjunto.

pub enum Signal {
    Hup,        // SIGHUP  = 1
    Int,        // SIGINT  = 2
    Quit,       // SIGQUIT = 3
    Kill,       // SIGKILL = 9
    Usr1,       // SIGUSR1 = 10
    Usr2,       // SIGUSR2 = 12
    Pipe,       // SIGPIPE = 13
    Term,       // SIGTERM = 15
    Other(i32), // qualquer signum não listado acima
}
Método Assinatura Descrição
to_int fn to_int(self: Signal) -> i32 Signum POSIX; Other(n) retorna n literalmente.
from_int fn from_int(n: i32) -> Signal Constrói a partir de um signum bruto; valores desconhecidos tornam-se Other(n). Sempre tem sucesso (total).
name fn name(self: Signal) -> string Nome no estilo POSIX ("SIGINT"); Other(n) retorna "SIG#<n>".
import stdlib::signal::*;

fn main() -> i32 {
    let s: Signal = Signal::Int;
    println!(s.name(), s.to_int());        // SIGINT 2

    let t: Signal = Signal::from_int(15);
    println!(t.name());                    // SIGTERM

    let o: Signal = Signal::Other(42);
    println!(o.name(), o.to_int());        // SIG#42 42

    match s {
        Int  => { println!("interrupt"); }
        Term => { println!("terminate"); }
        _    => { println!("other"); }
    }
    return 0;
}
import stdlib::signal::*;

// match não consegue comparar inteiros — converta para signum e ramifique com if/else.
fn classify(s: Signal) -> string {
    let n: i32 = s.to_int();
    if n == 2  { return "interrupt"; }
    if n == 15 { return "terminate"; }
    if n == 1  { return "hangup"; }
    return "other";
}

fn main() -> i32 {
    println!(classify(Signal::Int));            // interrupt
    println!(classify(Signal::Term));           // terminate
    println!(classify(Signal::from_int(99)));   // other
    return 0;
}

Inscrevendo-se — signal_chan

pub fn signal_chan(s: Signal) -> chan<Signal>

Inscreve-se em s. Cada ocorrência empurra o mesmo Signal no canal retornado. Idempotente — chamadas repetidas para o mesmo sinal retornam o mesmo canal (o slot é indexado pelo signum, limitado a 0..63). O buffer comporta 64; sinais entregues mais rápido do que são consumidos além desse limite são descartados (aceitável para sinais de desligamento, que chegam uma única vez).

import stdlib::signal::*;

fn main() -> !i32 {
    let usr1: chan<Signal> = signal_chan(Signal::Usr1);
    signal_raise(Signal::Usr1)?;
    let s: Signal = usr1.recv();
    println!("got", s.name());     // got SIGUSR1
    return ok(0);
}

Um servidor de verdade aciona o canal a partir de um loop, tipicamente junto com outros braços em um select!. recv() retorna Signal (não ?Signal), portanto um recv() simples bloqueia até que um sinal chegue:

import stdlib::signal::*;

fn run() -> !i32 {
    let intr: chan<Signal> = signal_chan(Signal::Int);
    let term: chan<Signal> = signal_chan(Signal::Term);
    signal_raise(Signal::Term)?;
    while true {
        let sig: Signal = term.recv();
        match sig {
            Int  => { println!("shutting down (int)"); break; }
            Term => { println!("shutting down (term)"); break; }
            _    => { }
        }
    }
    intr.close();        // silencia aviso de canal aberto; permite que receptores parem
    term.close();
    return ok(0);
}

fn main() -> !i32 {
    return run();
}

Alterando a disposição — signal_default, signal_ignore

pub fn signal_default(s: Signal) -> !
pub fn signal_ignore(s: Signal) -> !
Função Efeito
signal_default(s) Restaura o handler padrão do SO (SIG_DFL). Um canal de um signal_chan(s) anterior para de receber.
signal_ignore(s) Descarta s inteiramente (SIG_IGN) — sem canal, sem ação padrão. Comum para SIGPIPE em servidores que detectam desconexões no nível de leitura/escrita.

Ambas retornam ! (um Result sem valor); propague com ? ou use o padrão com ??. Elas produzem err("signal_default failed") / err("signal_ignore failed") se a chamada subjacente a sigaction (POSIX) falhar — na prática apenas para um signum fora do intervalo (< 0 ou >= 64).

import stdlib::signal::*;

fn main() -> !i32 {
    signal_ignore(Signal::Pipe)?;   // escritas em sockets fechados não nos matam
    signal_default(Signal::Int)?;   // Ctrl+C encerra novamente
    return ok(0);
}

Lançando — signal_raise

pub fn signal_raise(s: Signal) -> !

Lança s contra o processo atual. Se uma inscrição estiver ativa, o sinal é empurrado diretamente para o canal (confiável entre plataformas, sem round-trip pelo SO); caso contrário, cai de volta para raise(sig) no POSIX ou GenerateConsoleCtrlEvent no Windows. Graças ao caminho direto, um lançamento dentro do processo funciona para qualquer signum uma vez inscrito — inclusive SIGUSR1/SIGUSR2 no Windows, que não tem análogo no nível do SO. Isso o torna a forma idiomática de acionar um braço de select! em testes. Retorna err("signal_raise failed") em caso de falha.

_signal_push (helper interno)

pub fn _signal_push(c: chan<Signal>, n: i32)

Empurra Signal::from_int(n) em c. É pub apenas porque a thread leitora em C o chama do lado Glide (para que o runtime nunca precise do layout privado de Signal). Normalmente você não tem motivo para chamá-lo; prefira signal_raise para injetar um sinal em testes.

import stdlib::signal::*;

fn main() -> !i32 {
    let intr: chan<Signal> = signal_chan(Signal::Int);
    _signal_push(intr, 2);
    let s: Signal = intr.recv();
    println!(s.name());            // SIGINT
    return ok(0);
}

Backtraces

stdlib::backtrace percorre a pilha de chamadas ativa via o unwinder da plataforma (backtrace()/backtrace_symbols() no glibc/macOS/FreeBSD, CaptureStackBackTrace + SymFromAddr/SymGetLineFromAddr64 do DbgHelp no Windows). As linhas são aproximadas: com -O2 e omissão do frame pointer você pode obter menos frames ou menos detalhes, e em libcs sem execinfo.h (p. ex., musl) o resultado degrada para vazio.

Função Assinatura Descrição
stack_trace fn stack_trace() -> *Vector<string> Pilha de chamadas atual, um frame por elemento. A primeira entrada é o chamador de stack_trace; o próprio unwinder é filtrado.
stack_trace_skip fn stack_trace_skip(n: i32) -> *Vector<string> O mesmo, mas pula os n frames mais próximos do chamador — útil para ocultar um shim fino (p. ex., a expansão de uma macro log_error!). No Windows, n é limitado a 0..32.

Ambas retornam um vetor vazio quando a plataforma não consegue percorrer a pilha — nunca um erro ou ?T, portanto sempre verifique .len() == 0 em vez de presumir que existem frames. As strings de frame são o que o unwinder produz — tipicamente binary(function+0xoffset) [0xaddr] no POSIX e function (file:line) (quando há informação de linha) ou function+0xoffset no Windows.

import stdlib::backtrace::*;

fn buggy() {
    let frames: *Vector<string> = stack_trace();
    for f in frames {
        println!(f);
    }
    let skipped: *Vector<string> = stack_trace_skip(1);
    println!("frames", skipped.len());
}

fn main() -> i32 {
    buggy();
    return 0;
}

Uso defensivo — trate explicitamente o caso degradado (vazio) e acesse o frame do topo com .get(0):

import stdlib::backtrace::*;

fn here() {
    let frames: *Vector<string> = stack_trace();
    if frames.len() == 0 {
        println!("(no frames available)");   // p. ex. musl / build com FPO
        return;
    }
    println!("top frame:", frames.get(0));
    println!("frame count:", frames.len());
}

fn caller() { here(); }

fn main() -> i32 {
    caller();
    return 0;
}

Macros declarativas (macro name!)

Uma macro declarativa é um template no estilo macro_rules! que expande em tempo de compilação, entre o parse e a checagem de tipos. Os matchers vinculam os argumentos da chamada ($x:expr), e a forma variádica $($x:expr),* repete um fragmento do corpo com $( … )*:

macro bail!($cond:expr, $msg:expr) {
    if $cond { return err($msg); }
}

macro list_each!($($v:expr),*) {        // variádica
    $( println!($v); )*
}

// Atrelada a tipo: forma de instância (usa `self`) e a forma `Type::name!`.
impl<T> Vector<T> {
    macro push_all!($($x:expr),*) { $( self.push($x); )* }
}

Macros que retornam um valor

Quando o corpo de uma macro termina com return <expr>;, uma chamada em posição de expressão expande para uma expressão-bloco que produz esse valor — a mesma regra de um bloco literal { …; return v }. A mesma macro faz splice de statements em posição de statement e produz um valor quando usada como tal:

macro doubled!($x:expr) {
    let d: i32 = $x * 2;
    return d;
}

macro ints!($($x:expr),*) {
    let v: *Vector<i32> = Vector::new();
    $( v.push($x); )*
    return v;
}

let a = doubled!(21);                 // 42
let b = doubled!(5) + doubled!(10);   // 30 — dentro de uma expressão maior
let v = ints!(1, 2, 3);               // um *Vector<i32> de verdade

Isso funciona em todas as formas de chamada — name! simples, receptor recv.name! e Type::name! — e em qualquer posição de expressão: inicializador de let, argumento de função, operando ou return.

Higiene

As variáveis locais que o corpo da macro introduz são renomeadas por expansão, então a macro pode nomear um temporário como quiser sem colidir com uma variável do chamador de mesmo nome passada como argumento:

macro inc!($x:expr) {
    let tmp: i32 = $x + 1;
    return tmp;
}

let tmp: i32 = 5;
let y = inc!(tmp);   // y == 6 — o `tmp` de inc não captura o `tmp` do chamador

Uma macro aninhada nos argumentos de outra macro expande corretamente, e uma macro definida em termos de si mesma para num limite de recursão com um diagnóstico, em vez de travar o compilador.


Meta (superfície AST para macros)

stdlib::meta é o ponto de entrada curado e suportado para bibliotecas que criam macros @proc_macro / @proc_attr / @proc_derive. O corpo do módulo é um único re-export:

pub import bootstrap::ast::*;

Ele espelha deliberadamente os nomes internos de bootstrap::ast do compilador, mas protege as bibliotecas de macros de refatorações internas — importar stdlib::meta aparece como uma dependência normal, em vez de mexer nas entranhas do compilador. Uma proc macro é executada no interpretador embutido do compilador em tempo de compilação (antes da geração de código) e é removida do binário do consumidor, não adicionando nenhum peso em tempo de execução.

Uma macro no estilo de função recebe o *Vector<Expr> da chamada (os argumentos); uma macro de atributo/derive recebe o *Stmt anotado. Em ambos os casos, a saída é o *Vector<Stmt> intercalado no consumidor.

Structs de nós

Os tipos de nós AST que sua macro constrói e inspeciona. Todos os campos são pub; você lê p. ex. expr.kind, expr.int_val, expr.line, stmt.kind.

Struct Papel
Expr Nó de expressão. Campos-chave: kind: i32 (um EX_*), line/column, int_val, str_val, bool_val, op_code, lhs/rhs/operand: *Expr, args: *Vector<Expr>, field: string, cast_to: *Type.
Stmt Nó de instrução. kind: i32 (um ST_*) mais campos de payload por tipo.
Type Nó de tipo. kind: i32 (um TY_*).
Param Um parâmetro de função (nome + *Type).
Field Um campo de struct (nome + *Type + is_pub).
EnumVariant Uma variante de enum.
MatchArm Um braço de match.
SelectArm Um braço de select!.
Attr Um atributo (@nome(args)).
MacroParam Um parâmetro matcher em uma definição de macro.
StructLitField Um campo de um literal de struct.

Constantes de kind (discriminantes)

O campo kind de cada nó contém um desses discriminantes i32. O catálogo é grande; a tabela lista os prefixos e uma fatia representativa. O conjunto completo vive em bootstrap::ast e é re-exportado literalmente.

Prefixo Quantidade Exemplos
TY_* (tipos de tipo) 13 TY_NAMED=0, TY_POINTER=1, TY_BORROW=2, TY_SLICE=4, TY_GENERIC=5, TY_FNPTR=6, TY_RESULT=7 (!T), TY_OPTION=9 (?T), TY_TUPLE=12
EX_* (tipos de expr) 27 EX_INT=0, EX_FLOAT=1, EX_STRING=2, EX_BOOL=3, EX_IDENT=5, EX_BINARY=6, EX_UNARY=7, EX_CALL=8, EX_MEMBER=10, EX_MACRO=14, EX_IF=23, EX_MATCH=25
ST_* (tipos de stmt) 27 ST_LET=0, ST_RETURN=1, ST_EXPR=2, ST_IF=3, ST_WHILE=4, ST_FN=6, ST_STRUCT=9, ST_FOR=12, ST_ENUM=15, ST_MATCH=16, ST_TRAIT=24, ST_SELECT=26
UN_* (operadores unários) 7 UN_NEG=1, UN_NOT=2, UN_DEREF=3, UN_ADDR=4, UN_ADDR_MUT=5, UN_BIT_NOT=6, UN_TRY=7
OP_* (operadores binários/atribuição) 24 OP_ADD=1, OP_SUB=2, OP_MUL=3, OP_EQ=6, OP_NE=7, OP_LT=8, OP_AND=12, OP_OR=13, OP_ASSIGN=14, OP_COALESCE=24 (??)

Construtores

Construa AST de substituição. Há mais de 50; agrupados aqui, com assinaturas literais para os mais usados.

Grupo Funções
Tipos (ty_*) ty_named, ty_pointer, ty_generic, ty_fnptr, ty_result, ty_option, ty_optres, ty_assoc, ty_tuple
Expressões (expr_*) expr_int, expr_float, expr_string, expr_bool, expr_ident, expr_char, expr_null, expr_binary, expr_unary, expr_assign, expr_call, expr_member, expr_index, expr_cast, expr_path, expr_macro, expr_macro_var, expr_if, expr_match, expr_block, expr_tuple, expr_struct_lit, expr_fnexpr, expr_postinc, expr_postdec
Instruções (stmt_*) stmt_let, stmt_return, stmt_expr, stmt_fn, stmt_struct, stmt_impl, stmt_impl_trait, stmt_if, stmt_while, stmt_for, stmt_break, stmt_continue, stmt_const, stmt_import, stmt_enum, stmt_spawn, stmt_match, stmt_craw, stmt_asm
Helpers make_param, make_field, strip_ptr, ast_fill_defaults, pass_diag
pub fn expr_int(n: i64, line: i32, col: i32) -> *Expr            // nó de literal inteiro
pub fn expr_string(s: string, line: i32, col: i32) -> *Expr      // literal de string
pub fn expr_ident(name: string, line: i32, col: i32) -> *Expr    // identificador nu
pub fn expr_binary(op: i32, lhs: *Expr, rhs: *Expr) -> *Expr     // op é um código OP_*
pub fn expr_call(callee: *Expr, args: *Vector<Expr>) -> *Expr    // f(args)
pub fn stmt_let(name: string, ty: *Type, value: *Expr,
                is_mut: bool, line: i32, col: i32) -> *Stmt      // let name: ty = value;
pub fn stmt_expr(e: *Expr) -> *Stmt                              // envolve um Expr como Stmt
pub fn ty_named(name: string) -> *Type                          // um nome de tipo simples
pub fn pass_diag(line: i32, col: i32, severity: i32,
                 code: string, msg: string)                     // emite um diagnóstico

A proc macro mínima: answer!() expande para o literal 42 em cada ponto de chamada.

import stdlib::meta::*;

@proc_macro(answer)
fn impl_answer(args: *Vector<Expr>) -> *Vector<Stmt> {
    let out: *Vector<Stmt> = Vector::new();
    out.push(*stmt_expr(expr_int(42, 0, 0)));
    return out;
}

fn main() -> i32 {
    return 0;
}

Construir uma instrução composta — let x: i32 = 1 + 2; — encadeia ty_named, expr_int, expr_binary (com o código de operação OP_ADD) e stmt_let:

import stdlib::meta::*;

@proc_macro(one_plus_two)
fn impl_one_plus_two(args: *Vector<Expr>) -> *Vector<Stmt> {
    let out: *Vector<Stmt> = Vector::new();
    let sum: *Expr = expr_binary(OP_ADD, expr_int(1, 0, 0), expr_int(2, 0, 0));
    let s: *Stmt = stmt_let("x", ty_named("i32"), sum, false, 0, 0);
    out.push(*s);
    return out;
}

fn main() -> i32 {
    return 0;
}

Macros também podem inspecionar suas entradas lendo o campo kind em relação aos discriminantes EX_*:

import stdlib::meta::*;

// Emite uma string nomeando o kind da primeira expressão argumento.
@proc_macro(describe_first)
fn impl_describe_first(args: *Vector<Expr>) -> *Vector<Stmt> {
    let out: *Vector<Stmt> = Vector::new();
    let label: string = "unknown";
    if args.len() > 0 {
        let first: Expr = args.get(0);
        if first.kind == EX_INT    { label = "int"; }
        if first.kind == EX_STRING { label = "string"; }
        if first.kind == EX_IDENT  { label = "ident"; }
    }
    out.push(*stmt_expr(expr_string(label, 0, 0)));
    return out;
}

fn main() -> i32 {
    return 0;
}

pass_diag — diagnósticos customizados em tempo de compilação

pub fn pass_diag(line: i32, col: i32, severity: i32, code: string, msg: string)

Emite um diagnóstico a partir de uma macro (ou passe do compilador). severity 1 = erro (falha o build), 2 = aviso. code é um id curto em kebab-case que o usuário pode silenciar com @allow("<code>"). Passe o .line / .column do nó AST que você está sinalizando para que o erro aponte para a fonte correta.

import stdlib::meta::*;

// Avisa (severity 2) se a macro for chamada sem argumentos.
@proc_macro(no_empty)
fn impl_no_empty(args: *Vector<Expr>) -> *Vector<Stmt> {
    let out: *Vector<Stmt> = Vector::new();
    if args.len() == 0 {
        pass_diag(0, 0, 2, "no-empty", "no_empty! called with no arguments");
    }
    out.push(*stmt_expr(expr_int(0, 0, 0)));
    return out;
}

fn main() -> i32 {
    return 0;
}

Lint

stdlib::lint documenta os códigos de aviso embutidos do compilador e oferece um helper de tempo de execução. Não há executores de lint aqui — o linting acontece dentro do compilador; este módulo é a superfície estável para os nomes dos códigos mais um helper para construir códigos de lint customizados. (Para emitir um diagnóstico customizado programaticamente a partir de uma macro/passe, use pass_diag em stdlib::meta, acima.)

Códigos de aviso embutidos

Cada código abaixo pode ser silenciado com @allow("<code>") no item circundante (a função, um let interno, um bloco, um if/while, ou a instrução em questão). Múltiplos códigos: @allow("unfreed-alloc", "deprecated-fn"). A supressão segue a AST e não se propaga para cima — um @allow mais estreito não silencia avisos emitidos antes no mesmo escopo externo.

Constante Valor Disparado quando
LINT_DEPRECATED_FN "deprecated-fn" Chamada a uma função marcada com @deprecated.
LINT_UNSTABLE_FN "unstable-fn" Chamada a uma função marcada com @unstable.
LINT_UNFREED_ALLOC "unfreed-alloc" malloc(...) / __glide_palloc_make() sem um free correspondente.
LINT_ARENA_NOT_FREED "arena-not-freed" Arena::new(...) sem .free() / defer.
LINT_UNUSED_VAR "unused-var" Um let local nunca é lido.
LINT_UNUSED_PARAM "unused-param" Um parâmetro de função nunca é lido.
LINT_UNUSED_FN "unused-fn" Uma função nunca é chamada ou referenciada.
LINT_UNNECESSARY_MUT "unnecessary-mut" Um let mut que nunca é reatribuído.

Códigos embutidos adicionais que o compilador emite mas que não têm constante nomeada aqui: addr-of-temporary (retornar &Foo { … } / refs pendentes), missing-return (um caminho de função não--> void cai fora do fim), large-return (uma struct >64 bytes retornada por valor), chan-leak (um chan deixado aberto em um retorno), e lint:<category> (qualquer função marcada com @lint(...)).

Uma segunda família de códigos de código morto também é emitida sem constante nomeada. Cada um é silenciado por @allow("unused"), e cada um pula declarações pub, genéricas e com impl (qualquer coisa que possa ser alcançada de fora do arquivo ou através de uma trait):

  • unused-struct — uma struct nunca é construída ou referenciada.
  • unused-enum — um enum nunca é usado.
  • unused-const — um const de nível superior nunca é lido.
  • unused-variant — uma variante de enum nunca é construída ou usada em match.
  • redundant-import — o mesmo módulo é importado duas vezes em um arquivo.
import stdlib::lint::*;

fn main() -> i32 {
    println!(LINT_DEPRECATED_FN);          // deprecated-fn
    println!(LINT_UNSTABLE_FN);            // unstable-fn
    println!(LINT_UNFREED_ALLOC);          // unfreed-alloc
    println!(LINT_ARENA_NOT_FREED);        // arena-not-freed
    println!(LINT_UNUSED_VAR);             // unused-var
    println!(LINT_UNUSED_PARAM);           // unused-param
    println!(LINT_UNUSED_FN);              // unused-fn
    println!(LINT_UNNECESSARY_MUT);        // unnecessary-mut
    return 0;
}

Lints customizados — lint_code

pub fn lint_code(category: string) -> string

Constrói o código canônico lint:<category> a partir de um nome de categoria (literalmente "lint:".concat(category)). Útil quando um editor ou ferramenta de build filtra/formata diagnósticos de lint customizado programaticamente.

import stdlib::lint::*;

fn main() -> i32 {
    let code: string = lint_code("blocking_io");
    println!(code);                        // lint:blocking_io
    return 0;
}

Anexando e suprimindo lints

Uma função anotada com @lint("blocking_io", "reason") aparece em cada ponto de chamada:

output
! lint:blocking_io
  function `read_sync` is flagged by `lint:blocking_io`:
    blocks the coro scheduler — use spawn_thread

Suprima-o em um ponto de chamada com @allow("lint:blocking_io"). Tanto @lint quanto as formas nomeadas @deprecated se anexam à definição da função; o @allow vai na chamada (ou no item que a envolve):

import stdlib::lint::*;

@deprecated("use parse_v2 instead")
fn parse_v1(s: string) -> i32 { return 0; }

@lint("blocking_io", "blocks the coro scheduler — use spawn_thread")
fn read_sync(path: string) -> i32 { return 0; }

fn main() -> i32 {
    @allow("deprecated-fn")
    let a: i32 = parse_v1("x");

    @allow("lint:blocking_io")
    let b: i32 = read_sync("/etc/hosts");

    println!(a + b);
    return 0;
}