Como o Rust Lida com Closures: Fn, FnMut e FnOnce

Em Rust, closures são funções anônimas que capturam variáveis do ambiente ao redor, oferecendo grande flexibilidade. O sistema de closures do Rust é definido por três traits essenciais: Fn, FnMut e FnOnce. Cada uma dessas traits determina como as closures interagem com as variáveis capturadas, quantas vezes podem ser chamadas e se podem modificar o ambiente. Para dominar as closures em Rust e escrever código eficiente e seguro, é fundamental entender essas traits.

Este artigo oferece uma visão detalhada das três traitsFn, FnMut e FnOnce – abordando suas definições, casos de uso, métodos, cenários aplicáveis e melhores práticas, tudo isso com exemplos de código para ajudar na compreensão dos conceitos.

O que são Fn, FnMut e FnOnce?

Fn, FnMut e FnOnce são três traits definidas na biblioteca padrão de Rust para descrever o comportamento de closures (ou qualquer objeto que possa ser chamado). A principal diferença entre elas está na forma como acessam as variáveis capturadas e nas regras de propriedade quando são chamadas.

  • FnOnce: Indica que uma closure pode ser chamada apenas uma vez. Após ser chamada, a própria closure é consumida e não pode mais ser usada.
  • FnMut: Indica que uma closure pode ser chamada várias vezes e pode modificar as variáveis capturadas quando invocada.
  • Fn: Indica que uma closure pode ser chamada várias vezes e apenas lê as variáveis capturadas, sem modificá-las.

Existe uma relação de herança entre essas três traits:

  • Fn herda de FnMut, e FnMut herda de FnOnce.
  • Portanto, se uma closure implementa Fn, ela também implementa automaticamente FnMut e FnOnce; se implementa FnMut, também implementa FnOnce.

Definição de cada Trait

FnOnce

A trait FnOnce define um método call_once com a seguinte assinatura:

pub trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}
  • Características: call_once recebe self (em vez de &self ou &mut self), o que significa que, quando a closure é chamada, ela transfere a propriedade de si mesma e, portanto, só pode ser invocada uma vez.
  • Casos de uso: Adequado para cenários onde uma closure precisa mover variáveis capturadas ou realizar uma operação única.

FnMut

A trait FnMut define um método call_mut com a seguinte assinatura:

pub trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}
  • Características: call_mut recebe &mut self, permitindo que a closure modifique seu estado interno ou variáveis capturadas quando invocada, e pode ser chamada várias vezes.
  • Casos de uso: Adequado para cenários onde uma closure precisa modificar seu ambiente em várias chamadas.

Fn

A trait Fn define um método call com a seguinte assinatura:

pub trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}
  • Características: call recebe &self, significando que a closure apenas empresta a si mesma e as variáveis capturadas de forma imutável. Ela pode ser chamada várias vezes sem modificar o ambiente.
  • Casos de uso: Adequado para cenários onde uma closure precisa ser chamada várias vezes e apenas lê dados.

Como as closures implementam essas traits

O compilador de Rust determina automaticamente quais traits uma closure implementa com base em como ela usa as variáveis capturadas. Uma closure pode capturar variáveis de três maneiras:

  • Por valor (move): A closure assume a propriedade da variável.
  • Por referência mutável (&mut): A closure captura uma referência mutável à variável.
  • Por referência imutável (&): A closure captura uma referência imutável à variável.

A trait implementada depende de como a variável capturada é usada:

  • Implementa apenas FnOnce: A closure move a variável capturada.
  • Implementa FnMut e FnOnce: A closure modifica a variável capturada.
  • Implementa Fn, FnMut e FnOnce: A closure apenas lê a variável capturada.

Exemplos de código

Closure implementando FnOnce

fn main() {
    let s = String::from("hello");
    let closure = move || {
        drop(s); // Moves s and drops it
    };
    closure(); // Called once
    // closure(); // Error: Closure has been consumed
}

Explicação: A closure captura s por move, tomando a propriedade dela e descartando-a quando chamada. Como s é movida, a closure só pode ser chamada uma vez, fazendo com que implemente apenas FnOnce.

Closure implementando FnMut

fn main() {
    let mut s = String::from("hello");
    let mut closure = || {
        s.push_str(" world"); // Modifies s
    };
    closure(); // First call
    closure(); // Second call
    println!("{}", s); // Outputs "hello world world"
}

Explicação: A closure captura s por referência mutável e a modifica a cada chamada. Como precisa modificar seu ambiente, implementa FnMut e FnOnce.

Closure implementando Fn

fn main() {
    let s = String::from("hello");
    let closure = || {
        println!("{}", s); // Reads s without modification
    };
    closure(); // First call
    closure(); // Second call
}

Explicação: A closure captura s por referência imutável e apenas a lê sem modificação. Portanto, implementa Fn, FnMut e FnOnce.

Usando essas traits em parâmetros de função

Closures podem ser passadas como argumentos para funções, e as funções precisam especificar o comportamento de closure necessário usando trait bounds.

Usando FnOnce

fn call_once<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn main() {
    let s = String::from("hello");
    call_once(move || {
        drop(s);
    });
}

Explicação: call_once aceita uma closure FnOnce e a chama uma vez, tornando-o adequado para closures que movem variáveis capturadas.

Usando FnMut

fn call_mut<F>(mut f: F)
where
    F: FnMut(),
{
    f();
    f();
}

fn main() {
    let mut s = String::from("hello");
    call_mut(|| {
        s.push_str(" world");
    });
    println!("{}", s); // Outputs "hello world world"
}

Explicação: call_mut aceita uma closure FnMut e a chama duas vezes. A closure pode modificar a variável capturada. Note que f deve ser declarado como mut.

Usando Fn

fn call_fn<F>(f: F)
where
    F: Fn(),
{
    f();
    f();
}

fn main() {
    let s = String::from("hello");
    call_fn(|| {
        println!("{}", s);
    });
}

Explicação: call_fn aceita uma closure Fn e a chama duas vezes. A closure apenas lê a variável capturada.

Quando usar cada trait?

Escolher a trait certa depende do comportamento necessário para a closure:

FnOnce

  • Caso de uso: A closure é chamada apenas uma vez ou precisa mover variáveis capturadas.
  • Exemplo: Uma operação única que transfere a propriedade.

FnMut

  • Caso de uso: A closure precisa ser chamada várias vezes e modificar variáveis capturadas.
  • Exemplo: Um contador ou atualização de estado.

Fn

  • Caso de uso: A closure precisa ser chamada várias vezes e apenas lê variáveis capturadas.
  • Exemplo: Logging ou consultas de dados.

Ao projetar funções, selecionar a trait mais permissiva aumenta a flexibilidade. Por exemplo, FnOnce aceita todas as closures, mas limita a invocação, enquanto Fn permite múltiplas chamadas, mas requer imutabilidade.

Melhores práticas

  • Prefira Fn: Se uma closure não precisa modificar variáveis, use Fn para máxima compatibilidade.
  • Use FnMut quando a modificação for necessária: Escolha FnMut quando uma closure precisa atualizar o estado.
  • Use FnOnce para closures de uso único: Se uma closure move variáveis ou executa uma tarefa única, use FnOnce.
  • Escolha a trait certa para APIs: Use FnOnce para chamadas de uso único, Fn para múltiplas chamadas somente leitura e FnMut para múltiplas chamadas com modificações.
  • Esteja atento aos tempos de vida: Garanta que as variáveis capturadas vivam o suficiente para evitar erros de empréstimo.

Entender as Traits Fn em Rust é crucial para escrever código eficiente e seguro, permitindo um controle preciso sobre o comportamento das closures e suas interações com o ambiente.

Além disso, com a crescente demanda por desenvolvimento móvel, é interessante estar atento às novidades e tendências, como a possibilidade do Samsung Galaxy S25 Edge ter um design fino e durabilidade garantida.

Se você busca alternativas para aprender e praticar, uma boa opção é praticar inglês com IA.

Se você quer saber mais sobre tecnologia e o mundo do entretenimento, uma dica é ficar por dentro dos códigos de A Universal Time para março de 2025.

Para os fãs de jogos, vale a pena conferir os melhores jogos de heróis para PS5.

Com o avanço da tecnologia, é essencial estar sempre atualizado, e uma das formas de fazer isso é organizar seu feed do Google Discover de forma prática.

Este conteúdo foi auxiliado por Inteligência Artificial, mas escrito e revisado por um humano.

Leave a Comment

Exit mobile version