Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Óxido Un cierre es una función anónima que puede guardarse en una variable o pasarse como argumento a otras funciones. Puede crear un cierre en un lugar y luego realizar operaciones de cierre en un contexto diferente. A diferencia de las funciones, los cierres permiten capturar valores dentro del alcance en el que están definidos.
Un iterador es responsable de la lógica de atravesar cada elemento de la secuencia y determinar cuándo termina la secuencia. Cuando usamos iteradores, no necesitamos volver a implementar esta lógica.
Cierre, una estructura similar a una función que se puede almacenar en una variable.
En Rust, los cierres son funciones anónimas que capturan variables del entorno externo. El comportamiento de captura de un cierre depende de los tipos de variables y de cómo el cierre utiliza esas variables.
Captura por valor (Por valor)
Cuando un cierre captura una variable por valor, toma posesión de la variable. Esto significa que una vez creado el cierre, la variable original ya no se puede utilizar.
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
Captura por referencia (Por referencia)
Cuando un cierre captura una variable por referencia, toma prestada esa variable. Esto significa que la variable original todavía está disponible, pero el cierre solo puede tomarla prestada, no tomar posesión de ella.
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
Captura mutable
Un cierre puede capturar una referencia mutable, permitiéndole modificar el valor de la variable 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);
}
Si se quita el cierre mut
La modificación, la compilación falla.
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);
}
Mensaje de error: debido al préstamo de una variable count
,transferir closure
Se requiere enlace mutable.
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
cierre como argumento
Los cierres se pueden utilizar comopaso de parámetrosPara otras funciones, capturar variables en el entorno.
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);
}
En este ejemplo:
square
, que captura por referencia main
en funciónnumber
variable y calcular su cuadrado.call_closure
función, que acepta unaFn() -> i32
Los cierres vinculados a rasgos toman parámetros, lo que significa que el cierre no toma parámetros y devuelve uni32
tipo de valor.call_closure
función y voluntadsquare
Los cierres se pasan como argumentos.porquesquare
Los cierres no tienen parámetros, se pueden pasar directamente como parámetros acall_closure
。call_closure
La función llama al cierre e imprime el resultado. Hay muchas más diferencias entre funciones y cierres.Los cierres no siempre requieren algo comofn
Especifique los tipos de parámetros y valores de retorno como funciones. Las anotaciones de tipo son necesarias en las funciones porque son parte de la interfaz explícita expuesta al usuario. Es importante definir estrictamente estas interfaces para garantizar que todos tengan una comprensión coherente de las funciones utilizadas y los tipos de valores de retorno. Por el contrario, los cierres no se utilizan para dichas interfaces expuestas: se almacenan en variables y se utilizan sin nombrarlas ni exponerlas a los usuarios de la biblioteca.
Los cierres suelen ser breves y se relacionan con un contexto limitado más que con una situación arbitraria. En estos contextos restringidos, el compilador puede inferir de manera confiable los tipos de parámetros y valores de retorno, de manera similar a cómo puede inferir los tipos de la mayoría de las variables (aunque hay casos raros en los que el compilador requiere anotaciones de tipo de cierre).
Al igual que las variables, se pueden agregar anotaciones de tipo si deseamos aumentar la claridad y la claridad, con la desventaja de hacer que el código sea más detallado (en lugar de lo estrictamente necesario).
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
Con las anotaciones de tipo, la sintaxis de los cierres es más similar a la de las funciones. Aquí hay una comparación vertical de la definición de una función que agrega uno a su argumento y la sintaxis de cierre que tiene el mismo comportamiento. Se han agregado algunos espacios aquí para alinear las partes correspondientes. Esto muestra cuán similar es la sintaxis de cierre a la sintaxis de función, excepto por el uso de canalizaciones y alguna sintaxis 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 ;
Entonces el ejemplo anterior se puede simplificar a:
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
El compilador infiere un tipo concreto para cada parámetro y valor de retorno en la definición de cierre.
Veamos otro ejemplo:
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
Tenga en cuenta que esta definición de cierre no agrega ninguna anotación de tipo, por lo que podemos llamar a este cierre con cualquier tipo. Pero si intenta llamar al cierre dos veces, la primera vez usando i32 y la segunda vez usando f32, se informará un error:Se esperaba, ya que el cierre se llamó anteriormente con un argumento de 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
Una vez que un cierre captura una referencia o propiedad de un valor en el entorno en el que está definido (afectando así lo que, si lo hay, se mueve al cierre), el código dentro del cuerpo del cierre define la referencia más adelante cuando el cierre se evalúa O cómo se manipula el valor (lo que afecta lo que, en todo caso, se elimina del cierre). El cuerpo del cierre puede hacer cualquiera de las siguientes cosas: sacar un valor capturado del cierre, modificar el valor capturado, no mover ni modificar el valor, o nunca capturar el valor del entorno en primer lugar.
La forma en que un cierre captura y maneja valores en el entorno afecta los rasgos que implementa el cierre. Los rasgos son una forma para que las funciones y estructuras especifiquen los tipos de cierres que pueden usar.Dependiendo de cómo el cuerpo del cierre maneja los valores, el cierre implementa automática e incrementalmente uno, dos o tres.Fn
rasgo.
FnOnce
Se aplica a los cierres que se pueden llamar una vez. Todos los cierres implementan al menos este rasgo porque se pueden llamar a todos los cierres.Un cierre que saca los valores capturados del cuerpo del cierre solo implementaFnOnce
rasgo porque solo se puede llamar una vez.FnMut
Adecuado para cierres que no mueven el valor capturado fuera del cuerpo del cierre, pero pueden modificar el valor capturado. Este tipo de cierre se puede llamar varias veces.Fn
Se aplica a cierres que no sacan el valor capturado del cuerpo del cierre ni modifican el valor capturado y, por supuesto, incluyen cierres que no capturan el valor del entorno. Estos cierres se pueden llamar varias veces sin cambiar su entorno, lo cual es importante en escenarios donde los cierres se llaman varias veces al mismo tiempo.Aquí hay un ejemplo que muestra cómo usarlo en una función. FnOnce
como restricción genérica y garantizar que el cierre solo se llame una 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 de la operación
The value is: 42
Result of the closure: 42
En este ejemplo definimos una función genérica. call_once
, que acepta un tipo de F
Parámetrosf
,en F
debe lograrseFnOnce
rasgo.esto significaf
es un cierre que acepta parámetros vacíos y devuelve tipoT
el resultado de.
existir main
En la función, creamos un cierre.consume
, que captura value
de propiedad.Entonces, llamamoscall_once
función, pasadaconsume
Cierre.call_once
La función llama al cierre y devuelve su resultado.porqueconsume
El cierre se ha consumidovalue
, intenta llamar de nuevo call_once
Pasar el mismo cierre dará como resultado un error de compilación.
Aquí hay un uso FnMut
Ejemplos de rasgos.
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 de la operación
Result after applying increment: 5
Result after applying increment again: 15
En este ejemplo,apply_mut
La función acepta unaF
referencia mutable de tipo&mut F
como parámetro (por lo tanto,increment
El cierre no se moverá y se puede utilizar varias veces) y uni32
parámetros de tiponum
。where
cláusula especificaF
Debe ser una implementaciónFnMut
cierre, que acepta unai32
escribe parámetros y devuelve un tipo deT
el resultado de.main
Se crea un cierre mutable en la función.increment
, que modifica una variable capturada count
, luego usa apply_mut
función para llamar a este cierre.cada llamadaapply_mut
Utilizaráincrement
cierre, yincrement
Se modificarán los cierrescount
valor.
En óxido,Fn
El rasgo significa que un cierre no se apropia de las variables que captura, ni muta esas variables.El siguiente ejemplo muestra cómo definir una aceptaciónFn
Cierre como argumento de una función y utilícelo en escenarios concurrentes.
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();
}
}
No todas las ejecuciones se realizan en este orden. El subproceso que se ejecuta primero depende de la programación actual del sistema.
resultado de la operación
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
En este ejemplo:
call_once
función, que acepta un parámetro genéricoF
,y F
debe estar satisfechoFn(i32) -> T
límites de rasgos.esto significaF
es un cierre que acepta unai32
escribe parámetros y devuelve un tipo deT
el resultado de.main
En la función, definimos un cierre simple.double
, que acepta un i32
parámetros de tipox
y volverx * 2
el resultado de.map
Se crean 5 hilos y cada hilo se copiadouble
Cerrar y llamarlo en un hilo nuevo.call_once
función, pasando el cierre como argumento acall_once
。call_once
Se llama a la función, se ejecuta el cierre y se imprime el resultado.join
El método espera a que se completen todos los subprocesos.Los iteradores le permiten realizar cierto procesamiento en los elementos de una secuencia. A continuación, presentamos principalmente el uso de iteradores para procesar secuencias de elementos y la comparación de rendimiento de bucles VS iteradores. En Rust, los iteradores son vagos, lo que significa que no se realiza ninguna operación hasta que se llama al método que consume el iterador.
Todos los iteradores implementan una función llamada Iterator
se define en el rasgo de la biblioteca estándar. La definición de este rasgo se ve así:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
ySelf::Item
, definen el tipo asociado del rasgo.Pero por ahora todo lo que necesitas saber es que este código muestra cómo implementarIterator
Los rasgos requieren que unItem
escribe estoItem
El tipo se utiliza comonext
El tipo de valor de retorno del método. en otras palabras,Item
El tipo será el tipo del elemento devuelto por el iterador.
next
SíIterator
El único método que los implementadores deben definir.next
Devuelve un elemento a la vez en el iterador, encapsulado enSome
, cuando el iterador termina, regresaNone
。
Iterator
Los rasgos tienen un conjunto de métodos diferentes que se implementan de forma predeterminada proporcionados por la biblioteca estándar;Iterator
Todos estos métodos se encuentran en la documentación API de la biblioteca estándar del rasgo.Algunos métodos llaman a su definición.next
método, razón por la cual al implementarIterator
Se requiere que el rasgo sea implementado.next
razones del método.
Estas llamadas next
Los métodos de los métodos se denominan adaptadores consumidores porque llamarlos consume 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);
}
Si lo usas de nuevo numbers_iter
Se informará un error.sum
El método toma posesión del iterador y lo llama repetidamentenext
para iterar sobre el iterador, consumiendo así el iterador. A medida que itera sobre cada elemento, agrega cada elemento a una suma y devuelve la suma cuando se completa la iteración.transferirsum
Ya no se permite su uso.numbers_iter
, porque llamando sum
cuando toma posesión del iterador.
Iterator
Otra clase de métodos definidos en rasgos, llamados adaptadores de iterador, nos permiten cambiar el iterador actual por un iterador de un tipo diferente. Se pueden llamar varios adaptadores de iterador en una cadena. Sin embargo, debido a que todos los iteradores son diferidos, se debe llamar a un método de adaptador consumidor para obtener los resultados de la llamada del adaptador del 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]);
}
El método de recopilación consume el iterador y recopila los resultados en una estructura de datos.porquemap
Obtiene un cierre que le permite especificar cualquier operación que desee realizar en cada elemento que atraviesa.
Muchos adaptadores de iterador aceptan cierres como parámetros y, por lo general, el cierre especificado como parámetro del adaptador de iterador será el cierre que captura su entorno.
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 de la operación
The filtered and squared numbers are: [4, 16]
En este ejemplo,filter
ymap
Ambos son adaptadores de iteradores y aceptan cierres como parámetros. Estos cierres capturan su entorno, es decir, los elementos del iterador.existirfilter
En adaptador, cierre|&x| x % 2 == 0
Se utiliza para comprobar si el elemento es un número par; de ser así, el elemento se conservará.existirmap
En adaptador, cierre|x| x * x
Se utiliza para cuadrar cada elemento. Dado que estos adaptadores son de consumo, consumen el iterador original y, por lo tanto, no se pueden volver a utilizar. por fin,collect
El adaptador recoge los elementos procesados en un nuevoVec
medio.
Los iteradores, como abstracción de alto nivel, se compilan en código con aproximadamente el mismo rendimiento que el código de bajo nivel escrito a mano. Los iteradores son una de las abstracciones de costo cero de Rust, lo que significa que la abstracción no introduce sobrecarga de tiempo de ejecución.
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 de la operación
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
En este ejemplo puedes ver que los iteradores son todavía un poco más lentos.