Capítulo 04: Funções, Expressões vs Declarações e Escopo Léxico

Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance


🗺️ Mapa Conceitual de Avaliação de Expressões e Quadros de Pilha (Stack Frames)

flowchart TD
    Execucao["Execução Sequencial do Programa"] --> Chamada["Invocação de Função: fn calcular(x, y) -> T"]

    subgraph STACK_FRAME["Quadro de Pilha da Função (Stack Frame)"]
        Params["Parâmetros Locais (Copiados ou Movidos na Stack)"]
        Statements["Declarações (Statements com ';'): Avaliam para o Tipo Unit ()"]
        BlockExpr["Blocos de Escopo Intermediários: { let temp = ...; temp * 2 }"]
        FinalExpr["Expressão de Retorno Implícita (Sem ';'): Produz o Valor de Saída"]
    end

    Chamada --> STACK_FRAME
    Statements --> BlockExpr --> FinalExpr

    FinalExpr --> Retorno["Valor Retornado ao Chamador"]
    Retorno --> Desalocacao["Desalocação Instantânea do Stack Frame (SP Pointer Reset)"]

    style Execucao fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style Chamada fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style STACK_FRAME fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style Desalocacao fill:#e8f5e9,stroke:#4caf50,stroke-width:2px

🏛️ 1. O Paradigma de Expressões e Declarações

No Rust, a distinção entre Declarações (Statements) e Expressões (Expressions) é rigorosamente definida pelo compilador e dita a forma como o código é estruturado:

  1. Statements (Declarações): São instruções imperativas que executam uma ação mas não retornam um valor. Em Rust, declarações sempre terminam com ponto e vírgula (;). Exemplos: let x = 6; ou chamadas de função com ponto e vírgula no final. O valor avaliado de qualquer declaração é o tipo Unit ().
  2. Expressions (Expressões): Avaliam para um resultado concreto e não possuem ponto e vírgula no final. Em Rust, quase todas as construções (operações matemáticas, chamadas de função, blocos de código { ... }, if/else, match) são expressões.

⚙️ 2. Anatomia de Funções Idiomáticas em Rust

As funções em Rust são declaradas com a palavra-chave fn. Os parâmetros devem sempre ter seus tipos explicitamente anotados, assim como o tipo de retorno quando diferente de ():

// Parâmetros tipados e retorno formal com seta '-> Tipo'
fn calcular_vazao_litros_segundo(diametro_mm: f64, velocidade_m_s: f64) -> f64 {
    let raio_m = (diametro_mm / 1000.0) / 2.0;
    let area_m2 = std::f64::consts::PI * raio_m * raio_m;
    
    // RETORNO IMPLÍCITO:
    // Esta linha NÃO tem ponto e vírgula! O valor da expressão é retornado.
    area_m2 * velocidade_m_s * 1000.0
}

2.1 Por que o Ponto e Vírgula Faz Diferença?

Colocar um ponto e vírgula acidental na última linha de uma função transforma a expressão em um statement, fazendo a função retornar o tipo Unit () em vez do valor esperado:

fn soma(a: i32, b: i32) -> i32 {
    a + b; // ERRO: mismatched types! Retorna '()' em vez de 'i32'
}

2.2 Retorno Antecipado com return

O retorno implícito na última linha é o padrão idiomático do Rust. No entanto, para validações de guarda (guard clauses) ou condições de saída precoce, a palavra-chave return é perfeitamente válida e recomendada:

fn autenticar_sessao(token: &str) -> bool {
    if token.is_empty() {
        return false; // Early return imediato
    }
    
    token.starts_with("BEARER_AUTH_")
}

📦 3. Blocos de Escopo como Expressões de Primeira Classe

Um par de chaves { ... } cria um novo escopo léxico na memória e, simultaneamente, atua como uma expressão avaliada:

let taxa_servico = 15.0;

let custo_total = {
    let preco_base = 120.0;
    let desconto = 10.0;

    // A última linha do bloco é avaliada e atribuída a 'custo_total'
    (preco_base - desconto) + taxa_servico
};

println!("Custo Total Calculado: R$ {:.2}", custo_total);
// preco_base e desconto deixaram de existir aqui!

🧠 4. Escopo Léxico e Gestão na Memória Stack

A memória Stack organiza os dados em quadros de pilha (Stack Frames):

  • Ao invocar uma função, um novo quadro é alocado contendo os parâmetros e as variáveis locais da função.
  • O acesso a essas variáveis é instantâneo em tempo de execução através do registrador Stack Pointer (SP) da CPU.
  • Quando o fluxo de execução atinge o fechamento da chave }, o Stack Frame é imediatamente desalocado com zero custo computacional de varredura.

⚖️ 5. Comparativo: Statements vs Expressions

Característica Statement (Declaração) Expression (Expressão)
Termina com Ponto e Vírgula? SIM (obrigatoriamente ;) NÃO (se tiver ;, vira statement)
Produz Valor de Saída? Não (produz o tipo Unit ()) SIM (produz o tipo resultante T)
Exemplos Canônicos let a = 5;, println!("ok"); 5 + 5, { let x = 2; x * 10 }, if c { 1 } else { 2 }
Pode ser Atribuído a let? Não diretamente SIM (let y = { expr };)

⚠️ 6. Armadilhas Comuns e Mensagens do Compilador

  1. Erro de Ponto e Vírgula no Retorno de Função:
    error[E0308]: mismatched types
      --> src/main.rs:2:5
       |
     1 | fn obter_id() -> u32 {
       |                  --- expected `u32` because of return type
     2 |     100;
       |        ^ help: remove this semicolon
    

    Causa: O ponto e vírgula na linha 2 transformou 100 em statement retornando (). Correção: Remova o ponto e vírgula: 100.

  2. Tentar Acessar Variável Criada em Bloco Interno: Variáveis declaradas dentro de um bloco { let x = 10; } morrem assim que o bloco fecha. O compilador acusará cannot find value in this scope.

🛡️ 7. Boas Práticas e Idiomatismo Corporativo

  • Guards no Topo: Utilize guard clauses com return antecipado no topo das funções para rejeitar argumentos inválidos logo no início, mantendo o corpo principal da função sem aninhamentos profundos de if.
  • Prefira Retorno Implícito: Não use return na última linha da função. A comunidade Rust adota o retorno implícito como a forma idiomática padrão.

📋 8. Cheat-Sheet: Resumo Executivo

// Função pura com retorno implícito
fn dobro(n: i32) -> i32 { n * 2 }

// Early return
fn validar(x: i32) -> bool {
    if x < 0 { return false; }
    true
}

// Bloco de escopo como expressão
let valor = { let temp = 5; temp + 1 }; // valor = 6