minhas informações de contato
Correspondência[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Ferrugem Um encerramento é uma função anônima que pode ser salva em uma variável ou passada como argumento para outras funções. Você pode criar um fechamento em um local e depois executar operações de fechamento em um contexto diferente. Diferentemente das funções, os encerramentos permitem capturar valores dentro do escopo em que estão definidos.
Um iterador é responsável pela lógica de percorrer cada item da sequência e determinar quando a sequência termina. Ao usar iteradores, não precisamos reimplementar essa lógica.
Closure, uma estrutura semelhante a uma função que pode ser armazenada em uma variável.
No Rust, os encerramentos são funções anônimas que capturam variáveis do ambiente externo. O comportamento de captura de um encerramento depende dos tipos de variáveis e de como o encerramento utiliza essas variáveis.
Captura por valor (Por Valor)
Quando um encerramento captura uma variável por valor, ele assume a propriedade da variável. Isso significa que após a criação do encerramento, a variável original não poderá mais ser usada.
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
Capturar por referência (por referência)
Quando um encerramento captura uma variável por referência, ele toma emprestada essa variável. Isso significa que a variável original ainda está disponível, mas o encerramento só pode tomá-la emprestada, e não assumir a propriedade.
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
Captura Mutável
Um encerramento pode capturar uma referência mutável, permitindo modificar o valor da variável original.
fn main() {
let mut count = 0;
// 闭包通过可变引用捕获 count
let mut closure = || {
count += 1; // 修改 count 的值
println!("Count: {}", count);
};
closure(); // 打印 "Count: 1"
closure(); // 打印 "Count: 2"
// count 的值现在是 2
println!("Final count: {}", count);
}
Se o fechamento for removido mut
Modificação, compilação falha.
fn main() {
let mut count = 0;
// 闭包通过可变引用捕获 count
let closure = || {
count += 1; // 修改 count 的值
println!("Count: {}", count);
};
closure(); // 打印 "Count: 1"
closure(); // 打印 "Count: 2"
// count 的值现在是 2
println!("Final count: {}", count);
}
Avisos de mensagem de erro: Devido ao empréstimo de uma variável count
,transferir closure
A ligação mutável é necessária.
Compiling playground v0.0.1 (/playground)
error[E0596]: cannot borrow `closure` as mutable, as it is not declared as mutable
--> src/main.rs:4:9
|
4 | let closure = || {
| ^^^^^^^ not mutable
5 | count += 1; // 修改 count 的值
| ----- calling `closure` requires mutable binding due to mutable borrow of `count`
...
8 | closure(); // 打印 "Count: 1"
| ------- cannot borrow as mutable
9 | closure(); // 打印 "Count: 2"
| ------- cannot borrow as mutable
|
help: consider changing this to be mutable
|
4 | let mut closure = || {
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `playground` (bin "playground") due to 1 previous error
fechamento como argumento
Os fechos podem ser usados comoPassagem de parâmetrosPara outras funções, capture variáveis no ambiente.
fn main() {
// 创建一个整数变量
let number = 10;
// 创建一个闭包,它接受一个 i32 类型的参数并返回其平方
// 这里使用 || 表示这是一个闭包
let square = || number * number;
// 定义一个函数,它接受一个闭包作为参数并调用它
// 闭包作为参数需要指定其类型,这里使用 || -> i32 表示闭包没有参数并返回 i32 类型的值
fn call_closure<F>(f: F)
where
F: Fn() -> i32, // 使用 trait bound 指定闭包的签名
{
// 调用闭包并打印结果
let result = f();
println!("The result is: {}", result);
}
// 调用 `call_closure` 函数,并将闭包 `square` 作为参数传递
// 由于闭包 `square` 没有参数,我们可以直接传递
call_closure(square);
}
Neste exemplo:
square
, que captura por referência main
em funçãonumber
variável e calcule seu quadrado.call_closure
função, que aceita umFn() -> i32
Os fechamentos vinculados a características aceitam parâmetros, o que significa que o fechamento não aceita parâmetros e retorna umi32
tipo de valor.call_closure
função e vontadesquare
Os encerramentos são passados como argumentos.porquesquare
Closures não possuem parâmetros, podem ser passados diretamente como parâmetros paracall_closure
。call_closure
A função chama o fechamento e imprime o resultado. Existem muito mais diferenças entre funções e encerramentos.Os fechamentos nem sempre exigem algo comofn
Especifique os tipos de parâmetros e retorne valores como funções. As anotações de tipo são necessárias em funções porque fazem parte da interface explícita exposta ao usuário. É importante definir estritamente essas interfaces para garantir que todos tenham uma compreensão consistente das funções utilizadas e dos tipos de valores de retorno. Em contraste, os encerramentos não são usados para tais interfaces expostas: eles são armazenados em variáveis e usados sem nomeá-los ou expô-los aos usuários da biblioteca.
Os encerramentos são geralmente curtos e referem-se a um contexto restrito e não a qualquer situação arbitrária. Nesses contextos restritos, o compilador pode inferir com segurança os tipos de parâmetros e valores de retorno, semelhante à forma como pode inferir os tipos da maioria das variáveis (embora haja casos raros em que o compilador exija anotações de tipo de fechamento).
Semelhante às variáveis, as anotações de tipo podem ser adicionadas se desejarmos aumentar a clareza e clareza, com a desvantagem de tornar o código mais detalhado (em vez de estritamente necessário).
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
Com anotações de tipo, a sintaxe dos encerramentos é mais semelhante à das funções. Aqui está uma comparação vertical da definição de uma função que adiciona um ao seu argumento e a sintaxe de fechamento que tem o mesmo comportamento. Alguns espaços foram adicionados aqui para alinhar as partes correspondentes. Isso mostra como a sintaxe de fechamento é semelhante à sintaxe de função, exceto pelo uso de barras verticais e alguma sintaxe opcional:
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 ;
Portanto, o exemplo acima pode ser simplificado para:
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
O compilador infere um tipo concreto para cada parâmetro e valor de retorno na definição do encerramento.
Vejamos outro exemplo:
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
Observe que esta definição de encerramento não adiciona nenhuma anotação de tipo, portanto podemos chamar esse encerramento com qualquer tipo. Mas se você tentar chamar o encerramento duas vezes, a primeira vez usando i32 e a segunda vez usando f32, um erro será relatado:Esperado, já que o encerramento foi chamado anteriormente com um argumento do tipo "i32".
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:6:22
|
6 | let b1 = closure(a1);
| ------- ^^ expected `i32`, found `f32`
| |
| arguments to this function are incorrect
|
note: expected because the closure was earlier called with an argument of type `i32`
--> src/main.rs:5:21
|
5 | let b = closure(a);
| ------- ^ expected because this argument is of type `i32`
| |
| in this closure call
note: closure parameter defined here
--> src/main.rs:4:20
|
4 | let closure = |x| x;
| ^
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` (bin "playground") due to 1 previous error
Uma vez que um encerramento captura uma referência ou propriedade de um valor no ambiente em que é definido (afetando assim o que, se houver, é movido para o encerramento), o código dentro do corpo do encerramento define a referência posteriormente, quando o encerramento é avaliado ou como o valor é manipulado (o que afeta o que, se houver, é removido do fechamento). O corpo do encerramento pode fazer o seguinte: mover um valor capturado para fora do encerramento, modificar o valor capturado, não mover nem modificar o valor ou nunca capturar o valor do ambiente em primeiro lugar.
A maneira como um encerramento captura e trata valores no ambiente afeta as características que o encerramento implementa. Traits são uma forma de funções e estruturas especificarem os tipos de fechamentos que podem usar.Dependendo de como o corpo do fechamento lida com os valores, o fechamento implementa automática e incrementalmente um, dois ou trêsFn
característica.
FnOnce
Aplica-se a encerramentos que podem ser chamados uma vez. Todos os encerramentos implementam pelo menos esta característica porque todos os encerramentos podem ser chamados.Um fechamento que move valores capturados para fora do corpo do fechamento implementa apenasFnOnce
trait porque só pode ser chamado uma vez.FnMut
Adequado para fechamentos que não movem o valor capturado para fora do corpo do fechamento, mas podem modificar o valor capturado. Este tipo de fechamento pode ser chamado várias vezes.Fn
Aplica-se a encerramentos que não movem o valor capturado para fora do corpo do encerramento nem modificam o valor capturado e, claro, incluem encerramentos que não capturam o valor do ambiente. Esses fechamentos podem ser chamados diversas vezes sem alterar seu ambiente, o que é importante em cenários onde os fechamentos são chamados diversas vezes simultaneamente.Aqui está um exemplo mostrando como usá-lo em uma função FnOnce
como uma restrição genérica e garanta que o encerramento seja chamado apenas uma vez:
fn call_once<F, T>(f: F) -> T
where
F: FnOnce() -> T, // 约束 F 为 FnOnce trait,意味着它接受一个空参数并返回 T 类型
{
f() // 调用闭包并返回结果
}
fn main() {
// 创建一个闭包,它捕获了 `value` 的所有权
let value = 42;
let consume = move || {
let result = value; // 移动 `value`
println!("The value is: {}", result);
result // 返回结果
};
// 调用 `call_once` 函数,传入闭包
let result = call_once(consume);
println!("Result of the closure: {}", result);
// 尝试再次使用 `consume` 将会导致编译错误,因为它已经消耗了 `value`
// call_once(consume);
}
resultado da operação
The value is: 42
Result of the closure: 42
Neste exemplo definimos uma função genérica call_once
, que aceita um tipo de F
Parâmetrosf
,em F
Deve ser alcançadoFnOnce
característica.isso significaf
é um encerramento que aceita parâmetros vazios e retorna o tipoT
o resultado de.
existir main
Na função, criamos um encerramentoconsume
, que captura value
de propriedade.Então, chamamoscall_once
função, passada emconsume
Fecho.call_once
A função chama o encerramento e retorna seu resultado.porqueconsume
O fechamento foi consumidovalue
, tente ligar novamente call_once
Passar o mesmo encerramento resultará em um erro de compilação.
Aqui está um uso FnMut
Exemplos de características.
fn apply_mut<F, T>(func: &mut F, num: i32) -> T
where
F: FnMut(i32) -> T, // F 是一个可变闭包,接受一个 i32 类型的参数并返回类型为 T 的结果
{
func(num) // 调用闭包并返回结果
}
fn main() {
let mut count = 0;
// 创建一个闭包,它接受一个 i32 类型的参数并将其加到 count 上
let mut increment = |num: i32| -> i32 {
count += num;
count
};
// 使用 apply_mut 函数和 increment 闭包的引用
let result: i32 = apply_mut(&mut increment, 5);
println!("Result after applying increment: {}", result);
// 再次使用 apply_mut 函数和 increment 闭包的引用
let result: i32 = apply_mut(&mut increment, 10);
println!("Result after applying increment again: {}", result);
}
resultado da operação
Result after applying increment: 5
Result after applying increment again: 15
Neste exemplo,apply_mut
A função aceita umF
referência mutável de tipo&mut F
como parâmetro (então,increment
O fecho não será movido e pode ser usado várias vezes) e umi32
parâmetros de tiponum
。where
cláusula especificaF
Deve ser uma implementaçãoFnMut
fechamento, que aceita umi32
parâmetros de tipo e retorna um tipo deT
o resultado de.main
Um fechamento mutável é criado na funçãoincrement
, que modifica uma variável capturada count
, então use apply_mut
função para chamar esse fechamento.cada chamadaapply_mut
Usaráincrement
fechamento, eincrement
Os fechamentos serão modificadoscount
valor.
Na ferrugem,Fn
A característica significa que um encerramento não se apropria das variáveis que captura, nem altera essas variáveis.O exemplo a seguir mostra como definir uma aceitaçãoFn
Encerramento como argumento para uma função e usá-lo em cenários simultâneos.
use std::thread;
// 定义一个函数,它接受一个实现了 Fn(i32) -> i32 的闭包,并调用它
fn call_once<F, T>(func: F) -> T
where
F: Fn(i32) -> T, // 指定 F 是一个接受 i32 并返回 T 的 Fn 闭包
{
let result = func(42); // 调用闭包,传入一个 i32 类型的值
result // 返回闭包的执行结果
}
fn main() {
// 定义一个简单的 Fn 闭包,它接受一个 i32 类型的参数并返回两倍的该值
let double = |x: i32| -> i32 {
x * 2
};
// 创建多个线程,每个线程都使用相同的闭包
let handles: Vec<_> = (0..5).map(|i| {
let func = double; // 闭包可以被复制,因为它是 Fn 类型的
thread::spawn(move || {
let result = call_once(func); // 调用 call_once 函数,并传入闭包
println!("Thread {} result: {}", i, result); // 打印结果
})
}).collect();
// 等待所有线程完成
for handle in handles {
handle.join().unwrap();
}
}
Nem toda execução ocorre nesta ordem. Qual thread é executado primeiro depende do agendamento atual do sistema.
resultado da operação
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
Neste exemplo:
call_once
função, que aceita um parâmetro genéricoF
,e F
deve estar satisfeitoFn(i32) -> T
limites de característica.isso significaF
é um fechamento que aceita umi32
parâmetros de tipo e retorna um tipo deT
o resultado de.main
Na função, definimos um fechamento simplesdouble
, que aceita um i32
parâmetros de tipox
e retornox * 2
o resultado de.map
5 threads são criados e cada thread é copiadodouble
Fechar e chamá-lo em um novo tópicocall_once
função, passando o fechamento como argumento paracall_once
。call_once
A função é chamada, o fechamento é executado e o resultado é impresso.join
O método espera que todos os threads sejam concluídos.Os iteradores permitem realizar determinados processamentos nos itens de uma sequência. A seguir, apresentamos principalmente o uso de iteradores para processar sequências de elementos e a comparação de desempenho de loops versus iteradores. No Rust, os iteradores são preguiçosos, o que significa que nenhuma operação é executada até que o método que consome o iterador seja chamado.
Todos os iteradores implementam uma função chamada Iterator
é definido na característica da biblioteca padrão. A definição dessa característica é assim:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
eSelf::Item
, eles definem o tipo associado da característica.Mas por enquanto tudo que você precisa saber é que este código mostra como implementarIterator
As características exigem que umItem
tipo, esteItem
tipo é usado comonext
O tipo de valor de retorno do método. em outras palavras,Item
O tipo será o tipo do elemento retornado pelo iterador.
next
simIterator
O único método que os implementadores devem definir.next
Retorna um item por vez no iterador, encapsulado emSome
, quando o iterador termina, ele retornaNone
。
Iterator
traits possuem um conjunto de métodos diferentes que são implementados por padrão, fornecidos pela biblioteca padrão;Iterator
Todos esses métodos são encontrados na documentação da API da biblioteca padrão do trait.Alguns métodos chamam em sua definiçãonext
método, e é por isso que na implementaçãoIterator
a característica é necessária para ser implementadanext
razões do método.
Essas chamadas next
Os métodos dos métodos são chamados de adaptadores de consumo porque chamá-los consome iteradores.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 使用into_iter()将Vec转换为消费迭代器
let numbers_iter = numbers.iter();
let mut sum: i32 = numbers_iter
// 使用sum适配器计算迭代器中所有元素的总和
.sum();
// 打印总和
println!("The sum is: {}", sum);
}
Se você usá-lo novamente numbers_iter
Um erro será relatado.sum
O método assume a propriedade do iterador e o chama repetidamentenext
para iterar sobre o iterador, consumindo assim o iterador. À medida que itera sobre cada item, ele adiciona cada item a uma soma e retorna a soma quando a iteração é concluída.transferirsum
Não é mais permitido usarnumbers_iter
, porque ligando sum
quando assume a propriedade do iterador.
Iterator
Outra classe de métodos definidos em traits, chamados adaptadores de iteradores, nos permitem transformar o iterador atual em um iterador de um tipo diferente. Vários adaptadores iteradores podem ser chamados em uma cadeia. No entanto, como todos os iteradores são lentos, um método adaptador de consumo deve ser chamado para obter os resultados da chamada do adaptador iterador.
fn main() {
let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x * x).collect();
assert_eq!(v2, vec![1, 4, 9]);
}
O método collect consome o iterador e coleta os resultados em uma estrutura de dados.porquemap
Obtém um fechamento que permite especificar quaisquer operações que você deseja realizar em cada elemento que ele atravessa.
Muitos adaptadores iteradores aceitam fechamentos como parâmetros e, geralmente, o fechamento especificado como parâmetro do adaptador iterador será o fechamento que captura seu ambiente.
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// 使用into_iter()将Vec转换为消费迭代器
let filtered_and_squared: Vec<i32> = numbers.into_iter()
// 使用filter适配器过滤元素,接受一个闭包作为参数
// 闭包捕获其环境,这里指numbers的元素
.filter(|&x| x % 2 == 0) // 保留偶数
// 使用map适配器对过滤后的元素进行变换,也接受一个闭包
.map(|x| x * x) // 对每个元素进行平方
// 使用collect适配器将结果收集到一个新的Vec中
.collect();
// 打印过滤和平方后的结果
println!("The filtered and squared numbers are: {:?}", filtered_and_squared);
}
resultado da operação
The filtered and squared numbers are: [4, 16]
Neste exemplo,filter
emap
Ambos são adaptadores iteradores e aceitam fechamentos como parâmetros. Esses fechamentos capturam seu ambiente, ou seja, os elementos do iterador.existirfilter
No adaptador, fechamento|&x| x % 2 == 0
Usado para verificar se o elemento é um número par; nesse caso, o elemento será retido.existirmap
No adaptador, fechamento|x| x * x
Usado para quadrar cada elemento. Como esses adaptadores são consumidores, eles consomem o iterador original e, portanto, não podem ser usados novamente. afinal,collect
O adaptador coleta os elementos processados em um novoVec
meio.
Os iteradores, como abstração de alto nível, são compilados em código com aproximadamente o mesmo desempenho que o código de baixo nível escrito à mão. Iteradores são uma das abstrações de custo zero do Rust, o que significa que a abstração não introduz nenhuma sobrecarga de tempo de execução.
fn main() {
let numbers1 = (0..1000000).collect::<Vec<i64>>();
let numbers2 = (0..1000000).collect::<Vec<i64>>();
let mut sum1 = 0i64;
let mut sum2 = 0i64;
// 测量for循环的性能
let start = std::time::Instant::now();
for val in numbers1 {
sum1 += val;
}
let loop_duration = start.elapsed();
// 测量迭代器的性能
let start = std::time::Instant::now();
for val in numbers2.iter() {
sum2 += val;
}
let iterator_duration = start.elapsed();
println!("Iterator took: {:?}", iterator_duration);
println!("For loop took: {:?}", loop_duration);
}
resultado da operação
Iterator took: 12.796012ms
For loop took: 11.559512ms
Iterator took: 12.817732ms
For loop took: 11.687655ms
Iterator took: 12.75484ms
For loop took: 11.89468ms
Iterator took: 12.812022ms
For loop took: 11.785106ms
Iterator took: 12.78293ms
For loop took: 11.528941ms
Neste exemplo você pode ver que os iteradores ainda são um pouco mais lentos.