Closures
Closures em Rust são as funções anônimas, ou seja, funções que podem ou não ter algum nome. Vamos começar com exemplos mais simples e fáceis.
fn main() { let somar = |x, y| x + y; let valor = somar(1, 2); println!("{}", valor); }
O código acima tem como saída:
3
Os tipos para uma closure podem ser diversos, caso quiséssemos declarar o tipo da closure
faríamos da seguinte maneira:
fn main() { let somar = |x: i32, y: i32| x + y; let valor = somar(1, 2); println!("{}", valor); }
Precisamos desse tipo de anotação, pois nem sempre o compilador consegue distinguir o tipo dos argumentos.
Funções como argumento
É possível passar essas closures como o argumento de outra função. No exemplo abaixo, temos uma lista de números, transformamos em um iterador e chamamos o método for_each
passando como argumento a função anônima que escreve no terminal o valor de 'x'.
fn main() { let numeros = vec![1, 2, 3, 4]; numeros.iter().for_each(|x| println!("{}", x)); }
Podemos aproveitar do exemplo anterior e utilizar outra função, como o map para dar como saída outra coleção e fazer chamadas encadeadas.
fn main() { let numeros = vec![1, 2, 3, 4]; numeros .iter() .map(|x| { let mut valor = *x; if valor % 2 == 0 { valor += 10; } else { valor *= 10 } valor }) .for_each(|x| println!("{x}")) }
As closures são bem poderosas e podemos utilizar elas de forma a ganhar flexibilidade em nossos códigos.
Move?
Para este exemplo vamos implementar a sequência de Fibonacci, para isso vamos escrever uma função que retorna outra função. Espera... Uma função que retorna outra? Isso mesmo
fn fibonacci() -> impl FnMut() -> usize { let mut a = 0; let mut b = 1; return move || { a = b; b = a + b; return b - a; }; } fn main() { let mut f = fibonacci(); for i in 0..10 { println!("{}", f()); } }
O código acima ira nos devolver os 10 primeiros números da sequência de Fibonacci, aqui temos dois assuntos interessantes para tratar, o primeiro a palavra "impl" no retorno, o segundo a trait "FnMut" e uma segunda declaração do tipo de retorno? Como assim?
- impl: assim como a palavra reservada 'dyn', existe a palavra reservada 'impl' que pode ser usada para representar algum tipo dinâmico alocado na
stack
ao invés daheap
. - FnMut:
FnMut
assim comoFn
eFnOnce
, são traits que podem ser implementadas por Closures, sendo elas,FnMut
com valores mutáveis,Fn
com valores imutáveis eFnOnce
com valores imutáveis, sendo possível apenas uma única chamada. - Segunda declaração de retorno: essa segunda declaração de retorno é nada mais nada menos que a declaração do retorno dessa
FnMut
, que também pode ser receber parâmetros como, por exemplo:FnMut(i32) -> i32
, com argumentos na trait separados por virgula,FnMut(Box<Foo>, i32) -> Bar
;
No corpo de nossa função temos uma palavrinha nova ali que á o move
, esta palavra reservada indica que as variáveis 'a' e 'b', serão movidas para dentro do contexto da Closure.