2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Rust Closures are anonymous functions that can be stored in a variable or passed as arguments to other functions. Closures can be created in one place and then executed in different contexts. Unlike functions, closures allow you to capture values in the scope in which they are defined.
Iterators are responsible for traversing each item in a sequence and the logic of deciding when the sequence ends. When using iterators, we don't need to reimplement this logic.
A closure is a function-like structure that can be stored in a variable.
In Rust, closures are anonymous functions that can capture variables from the outer environment. The capture behavior of a closure depends on the types of the variables and how the closure uses those variables.
By Value
When a closure captures a variable by value, it takes ownership of that variable. This means that after the closure is created, the original variable cannot be used anymore.
fn main() {
let text = "Hello".to_string();
// 使用 move 来显式地表示闭包将获取 text 的所有权
let closure = move || println!("{}", text);
// 这里 text 不能被使用,因为其所有权已经被闭包获取
// println!("{}", text); // 这将导致编译错误
closure(); // 打印 "Hello"
}
Capture by Reference
When a closure captures a variable by reference, it borrows that variable. This means that the original variable is still available, but the closure can only borrow it and cannot take ownership.
fn main() {
let text = "Hello";
// 闭包通过引用捕获 text
let closure = || println!("{}", text);
// text 仍然可用,因为它没有被移动
println!("{}", text); // 打印 "Hello"
closure(); // 再次打印 "Hello"
}
Mutable Capture
A closure can capture a mutable reference, allowing it to modify the value of the original variable.
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);
}
If you remove the closure mut
Modification, compilation failed.
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);
}
Error message: Due to borrowing mutable count
,transfer closure
Requires mutable binding.
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
Closures as parameters
Closures can be used asParameter passingTo other functions, capture variables in the environment.
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);
}
In this example:
square
, which captures by reference main
In the functionnumber
variable and calculate its square.call_closure
A function that accepts aFn() -> i32
The closure that takes a trait bound as a parameter, which means that the closure takes no parameters and returns ai32
The value of type.call_closure
function, andsquare
The closure is passed as a parameter.square
Closures have no parameters, they can be passed directly as parameterscall_closure
。call_closure
The function calls the closure and prints out the result.There are more differences between functions and closures. Closures do not always require fn
Functions are written in the same way that types are annotated on their parameters and return values. Type annotations are needed on functions because they are part of the explicit interface exposed to users. Having a well-defined interface is important to ensure that everyone has a consistent understanding of the types of functions used and returned. Closures, by contrast, do not have such an exposed interface: they are stored in variables and used without naming them or exposing them to users of the library.
Closures are usually short and relevant only in a narrow range of contexts rather than in arbitrary situations. In these limited contexts, the compiler can reliably infer the types of the parameters and return value, similar to how it can infer the types of most variables (there are also rare cases where the compiler needs a closure type annotation).
Similar to variables, we can add type annotations if we want to increase explicitness and clarity, with the downside of making the code more verbose (versus strictly necessary).
fn main() {
let a = 100;
let add_one = |x: i32| -> i32 { x + 1 };
let b = add_one(a);
println!("{}", b);
}
With type annotations, closure syntax becomes more function-like. Here's a vertical comparison of a function definition that adds one to its parameter and the closure syntax for the same behavior. Some whitespace has been added to align the parts. This shows how similar closure syntax is to function syntax, except for the use of vertical bars and some optional syntax:
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 ;
So the above example can be simplified to:
fn main() {
let a = 100;
let add_one = |x| x + 1;
let b = add_one(a);
println!("{}", b);
}
The compiler infers a concrete type for each parameter and return value in a closure definition.
Let’s look at another example:
fn main() {
let a = 100i32;
let a1 = 100f32;
let closure = |x| x;
let b = closure(a);
let b1 = closure(a1);
println!("{}", b);
println!("{}", b1);
}
Note that this closure definition does not have any type annotations, so we can call this closure with any type. However, if we try to call the closure twice, the first time with i32 and the second time with f32, we will get an error:Expected, because the closure was called before with an argument of type '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
Once a closure captures a reference to or ownership of a value in the environment in which it is defined (thus affecting what gets moved into the closure, if any), the code in the closure body defines what happens to that reference or value later when the closure is evaluated (thus affecting what gets moved out of the closure, if any). The closure body can do any of the following: move a captured value out of the closure, modify a captured value, neither move nor modify the value, or never capture a value from the environment in the first place.
The way a closure captures and processes values from its environment affects the traits that the closure implements. Traits are how functions and structs specify the types of closures they can use. Depending on how the closure body processes values, the closure automatically and incrementally implements one, two, or three traits. Fn
trait。
FnOnce
Applies to closures that can be called once. All closures implement at least this trait, since all closures can be called. A closure that moves captured values out of its body only implementsFnOnce
trait, this is because it can only be called once.FnMut
Suitable for closures that do not move captured values out of the closure body, but may modify the captured values. Such closures can be called multiple times.Fn
Applicable to closures that neither move captured values out of the closure body nor modify captured values, and of course also include closures that do not capture values from the environment. Such closures can be called multiple times without changing their environment, which is very important in scenarios where closures are called multiple times concurrently.Here is an example showing how to use it in a function FnOnce
As a generic constraint, and to ensure that the closure is only called once:
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);
}
operation result
The value is: 42
Result of the closure: 42
In this example, we define a generic function call_once
, which accepts a type F
Parametersf
,in F
Must be achievedFnOnce
trait. This meansf
is a closure that takes empty arguments and returns a typeT
the result of.
exist main
In the function, we create a closureconsume
, which captures value
Then, we callcall_once
Function, passed inconsume
Closure.call_once
The function calls the closure and returns its result.consume
The closure has been consumedvalue
Try calling again call_once
Passing the same closure will result in a compilation error.
Here is a sample using FnMut
Example of a trait.
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);
}
operation result
Result after applying increment: 5
Result after applying increment again: 15
In this example,apply_mut
The function accepts aF
Mutable references to types&mut F
as a parameter (so that,increment
The closure will not be moved and can be used multiple times) and ai32
Type Parametersnum
。where
The clause specifiesF
Must be a realizedFnMut
A closure that accepts ai32
Type of parameter and returns a type ofT
the result of.main
A mutable closure is created in the functionincrement
, which modifies a captured variable count
, then use apply_mut
function to call this closure. Each time it is calledapply_mut
Will useincrement
closure, andincrement
Closures will be modifiedcount
The value of .
In Rust,Fn
The trait indicates that the closure does not take ownership of the variables it captures and does not mutate them. The following example shows how to define a closure that acceptsFn
A function that takes a closure as a parameter and uses it in concurrent scenarios.
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();
}
}
Not every execution is in this order. Which thread runs first depends on the system's current scheduling.
operation result
Thread 1 result: 84
Thread 2 result: 84
Thread 0 result: 84
Thread 4 result: 84
Thread 3 result: 84
In this example:
call_once
Function that accepts a generic parameterF
,and F
Must meetFn(i32) -> T
This means thatF
is a closure that accepts ai32
Type of parameter and returns a type ofT
the result of.main
In the function, we define a simple closuredouble
, which accepts a i32
Type Parametersx
and returnsx * 2
the result of.map
Five threads are created, each of which copiesdouble
Closure, and call in a new threadcall_once
function, passing the closure as a parametercall_once
。call_once
The function is called, the closure is executed, and the result is printed.join
Method waits for all threads to complete.Iterators allow you to do something with the items in a sequence. The following mainly introduces the use of iterators to process element sequences and the performance comparison of loops VS iterators. In Rust, iterators are lazy, which means that no operation is performed until a method that consumes the iterator is called.
Iterators all implement a Iterator
is defined in the standard library as a trait. The trait definition looks like this:
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 此处省略了方法的默认实现
}
type Item
andSelf::Item
, which define the associated type of the trait. But for now, just know that this code shows that the implementation Iterator
The trait requires that aItem
Type, thisItem
Type is used asnext
The return type of the method. In other words,Item
The type will be the type of elements returned by the iterator.
next
yesIterator
The only method implementors are required to define.next
Returns one item in the iterator at a time, wrapped inSome
When the iterator is finished, it returnsNone
。
Iterator
The trait has a number of different methods with default implementations provided by the standard library; you canIterator
All of these methods can be found in the standard library API documentation for the trait. Some methods callnext
method, which is why in the implementationIterator
Trait requires implementationnext
The reason for the method.
These calls next
Methods that call iterators are called consuming adaptors because calling them consumes the iterator.
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);
}
If you use it again numbers_iter
It will report an error.sum
method takes ownership of the iterator and calls it repeatedlynext
to iterate over the iterator, thereby consuming the iterator. As it iterates over each item, it adds each item to a sum and returns the sum when the iteration is complete.sum
No longer allowed to usenumbers_iter
, because calling sum
It takes ownership of the iterator.
Iterator
Another class of methods defined in the trait are called iterator adaptors, which allow us to change the current iterator into a different type of iterator. Multiple iterator adapters can be chained together. However, since all iterators are lazy, a consumer adapter method must be called in order to get the result of the iterator adapter call.
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]);
}
The collect method consumes iterators and collects the results into a data structure. map
Takes a closure that specifies any actions you wish to perform on each element in the traversal.
Many iterator adapters accept closures as arguments, and typically the closure specified as an argument to an iterator adapter will be a closure that captures its environment.
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);
}
operation result
The filtered and squared numbers are: [4, 16]
In this example,filter
andmap
are iterator adapters that accept closures as parameters. These closures can capture their environment, i.e. the elements in the iterator.filter
In the adapter, closure|&x| x % 2 == 0
It is used to check whether the element is an even number, if so, the element will be retained.map
In the adapter, closure|x| x * x
is used to square each element. Since these adapters are consuming, they consume the original iterator and cannot be used again. Finally,collect
The adapter collects the processed elements into a newVec
middle.
Iterators, as a high-level abstraction, are compiled into code that is roughly consistent with the low-level code written by hand. Iterators are one of Rust's zero-cost abstractions, which means that the abstraction does not introduce runtime overhead.
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);
}
operation result
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
From this example you can see that iterators are still slightly slower.