Clube do Livro #13
Clube do Livro #13
Clube do Livro #13
Clube do Livro #13
let expensive_closure = |num| {
println!("Calculando lentamente...");
thread::sleep(Duration::from_secs(2));
num
};Clube do Livro #13
Clube do Livro #13
let expensive_closure = |num: u32| -> u32 {
println!("Calculando lentamente...");
thread::sleep(Duration::from_secs(2));
num
};Clube do Livro #13
fn
fn add_one_v1 (x: u32) -> u32 { x + 1 }
let add_one_v2 = |x: u32| -> u32 { x + 1 };
let add_one_v3 = |x| { x + 1 };
let add_one_v4 = |x| x + 1 ;Clube do Livro #13
Observe que invocar as versões add_one_v3 e add_one_v4 pelo menos uma vez é obrigatório para que o código compile, pois somente assim o compilador poderá inferir os tipos e gerar as versões concretas.
Para cada closure, apenas uma versão concreta, com tipos definidos para os argumentos, será criada
Retornando ao projeto, ainda temos que resolver o problema da chamada dupla
Poderíamos usar uma variável, mas existe uma solução mais elegante que o uso de closures nos permite
O que nós iremos fazer é criar uma estrutura que guarde a nossa closure e o resultado de sua invocação: um sistema de cache bem simples. Dessa forma, o resultado será calculado apenas uma vez.
Com isso utilizaremos as técnicas memoization (memoização) e lazy evaluation/call-by-need (avaliação preguiçosa/chamada de acordo com necessidade)
Clube do Livro #13
Para fazer essa estrutura, precisaremos especificar o tipo da closure, pois os tipos dos campos de uma struct precisam ser explicitamente definidos
Cada closure tem seu próprio tipo anônimo, ou seja, mesmo que duas closures tenham a mesma assinatura, seus tipos são individuais
Dessa forma, para definir a nossa estrutura, nós teremos que lançar mão de genéricos e trait bounds (capítulo 10)
A biblioteca-padrão do Rust provê três tipos de traits para closures: Fn, FnMut ou FnOnce. Nós vamos discutir as diferenças entre elas depois.
Clube do Livro #13
Sempre o mesmo valor, mesmo com invocações diferentes.
Podemos usar um hashmap para resolver isso.
Só aceita closures com a assinatura Fn(u32) -> u32
Podemos usar mais tipos genéricos para dar a solução
Clube do Livro #13
Diferente de funções, closures podem capturar o ambiente e ter acesso às variáveis do contexto em que foram definidas:
fn main() {
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
}Clube do Livro #13
A captura de valores resulta numa sobrecarga para o app, pois a closure tem que usar acesso à memória.
Se você não precisa, não use.
Três formas de captura de valores para uma closure:
pegando a propriedade (FnOnce);
pegando emprestado como uma referência mutável (FnMut); ou
pegando emprestado como uma referência imutável (Fn).
O compilador automaticamente escolhe qual trait usar de acordo com o uso que a nossa closure faz das variáveis
Clube do Livro #13
Palavra-chave move: forçando tomada de propriedade
fn main() {
let x = vec![1, 2, 3];
let equal_to_x = move |z| z == x;
println!("ERRO: x foi movido: {:?}", x);
let y = vec![1, 2, 3];
assert!(equal_to_x(y));
}Na maior parte das vezes, você pode começar com a trait Fn; o compilador te avisará se você precisar de outra trait.
Clube do Livro #13
Clube do Livro #13
É um design pattern (padrão de código)
Consiste na execução algumas tarefas em uma sequência de itens por vez
Um iterador é responsável pela lógica de iterar sobre cada item e determinar quando a sequência termina
Ao usar iteradores já prontos do Rust, você não precisa reimplementar essa lógica sozinho;
Iteradores no Rust são preguiçosos (lazy-evaluation): eles não têm efeito até que você chame algum método que os consuma;
Clube do Livro #13
#[test]
fn iterator_sum() {
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
let total: i32 = v1_iter.sum();
assert_eq!(total, 6);
}Collect() é um muito usado: transforma um iterador numa coleção
Clube do Livro #13
Você pode interligar múltiplas chamadas a esses métodos para fazer transformações complexas com dados:
#[test]
fn iterator_sum() {
let v1: Vec<i32> = vec![1, 2, 3, 5, 6];
let v1_plus_even: Vec<i32> = v1.iter()
.map(|x| x + 1)
.filter(|x| x % 2 == 0)
.collect();
assert_eq!(v1_plus_even, vec![2,4,6]);
}Clube do Livro #13
#[test]
fn iterator_demonstration() {
let v1 = vec![1, 2, 3];
let mut v1_iter = v1.iter();
assert_eq!(v1_iter.next(), Some(&1));
assert_eq!(v1_iter.next(), Some(&2));
assert_eq!(v1_iter.next(), Some(&3));
assert_eq!(v1_iter.next(), None);
}Clube do Livro #13
Existem três formas de criar um iterador de uma sequência:
iter() produz um iterador sobre referências imutáveis
into_iter() produz um iterador que se apropria da coleção e retorna valores com propriedade
iter_mut() produz um iterador com referências mutáveis
Clube do Livro #13
Iterator
pub trait Iterator {
type Item; // capítulo 19
fn next(&mut self) -> Option<Self::Item>;
// métodos com implementação padrão
// omitidos
}Clube do Livro #13
next e o tipo associado Item.Você pode criar iteradores para os tipos embutidos de Rust, como, por-exemplo, um hashmap ou uma struct.
Você também pode criar iteradores para os seus tipos customizados ao implementar a trait Iterator
Clube do Livro #13
Clube do Livro #13
Construímos um programa de linha de comando no capítulo anterior
Com o conhecimento adquirido sobre closures e iteradores, nós vamos fazer algumas refatorações:
Utilizaremos o iterador retornado por args diretamente, removendo a necessidade do clone() e de slices de strings
Refatoraremos search() para um estilo funcional com iteradores
Clube do Livro #13
Clube do Livro #13
Embora sejam uma abstração de alto nível, iteradores são compilados quase no mesmo código de baixo-nível que você geraria
Iteradores são uma das abstrações de custo zero de Rust: o uso da abstração não impõe sobrecarga de tempo de execução adicional.
Otimização unrolling: remove a sobrecarga do código de controle do loop e, em vez disso, gera código repetitivo para cada iteração do loop. Todos os coeficientes são armazenados em registradores, o que significa que o acesso aos valores é muito rápido.
Clube do Livro #13
@felubra
twitter, github, telegram:
Clube do Livro #13