Resumo: Closure no Rust
O que é Closure?
O termo closure é bastante utilizado quando se comenta sobre programação funcional, podendo ser definido como, segundo MDN Web Docs, uma função que se "lembra" do ambiente — ou escopo léxico — em que ela foi criada.
Na obra, Functional Thinking: Paradigm Over Syntax, um closure é definido, em tradução livre, como uma função que carrega implicitamente as definições para todas as variáveis internamente referenciadas. Em outras palavras, a função "engloba" o contexto em volta das "coisas" que ela referencia.
Se essas definições não forem tão claras, como eu penso que não são, e se uma imagem fala por mil palavras, então um vídeo é um enciclopédia; O canal Código Fonte TV fez um ótimo sobre o assunto, que acredito ajudar bastante no entendimento do tema.
Julgo que um bom resumo seria definir um closure como "algo" que permite funções que vivem dentro de outras funções possam acessar o escopo da função mais externa. Confuso? Também! Vamos ver um exemplo.
fn soma_com_x(valor: i32) -> impl Fn() -> i32 {
let x = 10; // Variável que pertence ao escopo mais externo
// move aqui tem o papel de transferir o valor `x` para a closure
move || {valor + x} // Closure
}
fn contatador() -> impl FnMut() -> i32 {
// Como a função que é retorna altera o estado da função mais externa, é
// necessário o uso do Trait FnMut
let mut i = 0;
move || { // Função anônima que actual como closure
i = i + 1;
i
}
}
fn main() {
let closure_soma_com_x = soma_com_x(5);
let valor = closure(); // valor = 15
println!("{}", valor); // 15
let mut closure_contador = contatador(); // Retorna a closure
// É necessário a mutabilidade pelo fato da alteração do estado interno
closure_contador(); // i = 1
closure_contador(); // i = 2
closure_contador(); // i = 3
closure_contador(); // i = 4
println!("{}", closure_contador()); // i = 5
}
Hands-On Functional Programming in Rust diz que um closure é um objeto que atua como uma função, implementando
fn
,Fn
,FnMut
ouFnOnce
[...] os Trait citados são automaticamente implementados se permitido quando há a passagem de uma função ou um lambda. (modificado e traduzido).
Para utilizar uma função como parâmetro ou como tipo de retorno, função de alta ordem, é necessário o uso do Trait adequado para o propósito. Por exemplo: se a necessidade é um closure mais simples, para ser usada em single thread e que apenas consume as variáveis, o Trait Fn é suficiente; se há necessidade de alterar os valores internos, um FnMut é a opção correta, já se esse closure vai ser enviada para dentro de Threads, FnOnce, que permite a execução única dessa função, é a alterativa preferida.
Não se pode esquecer das palavras reservadas
dyn
eimpl
conforme a necessidade
Move
Conhecendo o Borrow Checker e Ownership do Rust e suas peculiaridades em relação a controle de memória, podemos dizer haver um detalhe não dito acima. Move
converte qualquer variável capturada por referência ou referência mutável para valores que o closure "possui", Owned, permitindo o comportado visto. A saída do compilador quando o Move
não é especificado é bem esclarecedora, como sempre.
error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
--> src/main.rs:82:5
|
82 | || {
| ^^ may outlive borrowed value `i`
83 | i = i + 1;
| - `i` is borrowed here
|
note: closure is returned here
--> src/main.rs:80:20
|
80 | fn contatador() -> impl FnMut() -> i32 {
| ^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `i` (and any other referenced variables), use the `move` keyword
|
82 | move || {
| ^^^^^^^
Como curiosidade, Move
e bastante utilizado com Threads, quando é necessário enviar esses dados da principal para as filhas; claro, há mais detalhes que não são tão relacionados com as closures, e não é o foco desse blog, mas que vale a pena se aprofundar.
É isso; um resuminho rápido, simples e direto ao ponto. Se for necessário maior aprofundamento no tema, deixo alguns links que usei para estudar sobre.
Agradeço pela leitura e fique a vontade para comentar, compartilhar ou se algo não estive correto, dá sugestões de melhoria, o que eu vou agradecer muito, já que esses blogs são mais para aprendizado mesmo.
Links relacionados
- https://doc.rust-lang.org/book/ch13-01-closures.html
- https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Closures
- https://doc.rust-lang.org/reference/types/closure.html
- https://doc.rust-lang.org/rust-by-example/fn/closures.html
- https://doc.rust-lang.org/book/ch19-05-advanced-functions-and-closures.html
- https://doc.rust-lang.org/rust-by-example/fn/closures/capture.htmlhttps://stevedonovan.github.io/rustifications/2018/08/18/rust-closures-are-hard.html