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 traits – Fn, 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
recebeself
(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 eFnMut
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.