闭包(Closures)只是能够捕获上下文(context)的Fn
,FnMut
或者FnOnce
类型的函数。
它们的参数是在一对竖线(|
)里用逗号隔开的名称列表。它们不需要大括号(curly braces),除非你想写多个声明(statement)。
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
for_each_planet(|planet| println!("Hello, {}", planet));
}
// prints:
// Hello, Earth
// Hello, Mars
// Hello, Jupiter
借用规则同样适用于闭包:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str)
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// our closure borrows `greeting`, so it cannot outlive it
}
例如,下面的代码是行不通的:
fn for_each_planet<F>(f: F)
where F: Fn(&'static str) + 'static // `F` must now have "'static" lifetime
{
f("Earth");
f("Mars");
f("Jupiter");
}
fn main() {
let greeting = String::from("Good to see you");
for_each_planet(|planet| println!("{}, {}", greeting, planet));
// error: closure may outlive the current function, but it borrows
// `greeting`, which is owned by the current function
}
但是下面的代码是行得通的:
fn main() {
let greeting = String::from("You're doing great");
for_each_planet(move |planet| println!("{}, {}", greeting, planet));
// `greeting` is no longer borrowed, it is *moved* into
// the closure.
}
一个FnMut
被调用的时候需要是可变借用,所以它在使用的时候只能被调用一次。
下面的调用是合法的:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
下面代码则不行:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
println!("{}", f(f(2)));
// error: cannot borrow `f` as mutable more than once at a time
}
fn main() {
foobar(|x| x * 2);
}
下面的代码又合法了:
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
foobar(|x| x * 2);
}
// output: 8
FnMut
会存在是因为一些闭包会可变借用局部变量(local variables):
fn foobar<F>(mut f: F)
where F: FnMut(i32) -> i32
{
let tmp = f(2);
println!("{}", f(tmp));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
x * acc
});
}
// output: 24
这些闭包不能被传递期望是Fn
类型的函数:
fn foobar<F>(f: F)
where F: Fn(i32) -> i32
{
println!("{}", f(f(2)));
}
fn main() {
let mut acc = 2;
foobar(|x| {
acc += 1;
// error: cannot assign to `acc`, as it is a
// captured variable in a `Fn` closure.
// the compiler suggests "changing foobar
// to accept closures that implement `FnMut`"
x * acc
});
}
FnOnce
闭包智能被调用一次,它们存在因为一些闭包在捕获变量的时候会把已经移动进闭包的变量再次移出来:
fn foobar<F>(f: F)
where F: FnOnce() -> String
{
println!("{}", f());
println!("{}", f());
// error: use of moved value: `f`
}
并且,如果你需要确认我们的闭包确实移动(move)了s
,下面也是不合法的:
fn main() {
let s = String::from("alright");
foobar(move || s);
foobar(move || s);
// use of moved value: `s`
}
但是下面这样写是可以的:
fn main() {
let s = String::from("alright");
foobar(|| s.clone());
foobar(|| s.clone());
}
下面是一个带有两个参数的闭包:
fn foobar<F>(x: i32, y: i32, is_greater: F)
where F: Fn(i32, i32) -> bool
{
let (greater, smaller) = if is_greater(x, y) {
(x, y)
} else {
(y, x)
};
println!("{} is greater than {}", greater, smaller);
}
fn main() {
foobar(32, 64, |x, y| x > y);
}
下面是一个把两个参数都忽略的闭包:
fn main() {
foobar(32, 64, |_, _| panic!("Comparing is futile!"));
}
这里是一个轻微让人有点担忧的闭包:
fn countdown<F>(count: usize, tick: F)
where F: Fn(usize)
{
for i in (1..=count).rev() {
tick(i);
}
}
fn main() {
countdown(3, |i| println!("tick {}...", i));
}
// output:
// tick 3...
// tick 2...
// tick 1...
这里是一个马桶闭包(toilet closure):
fn main() {
countdown(3, |_| ());
}
这样叫是因为|_|()
看起来想一个马桶。
任何可以迭代的都可以在for in
循环中使用。
我们刚刚见到过一个range被用在for in
循环里,对于Vec
也可以使用:
fn main() {
for i in vec![52, 49, 21] {
println!("I like the number {}", i);
}
}
或者是一个切片(slice):
fn main() {
for i in &[52, 49, 21] {
println!("I like the number {}", i);
}
}
// output:
// I like the number 52
// I like the number 49
// I like the number 21
或者是一个真实的迭代器:
fn main() {
// note: `&str` also has a `.bytes()` iterator.
// Rust's `char` type is a "Unicode scalar value"
for c in "rust".chars() {
println!("Give me a {}", c);
}
}
// output:
// Give me a r
// Give me a u
// Give me a s
// Give me a t
即使迭代项被过滤(filtered),映射(mapped)和单一化(flattened):
fn main() {
for c in "sHE'S brOKen"
.chars()
.filter(|c| c.is_uppercase() || !c.is_ascii_alphabetic())
.flat_map(|c| c.to_lowercase())
{
print!("{}", c);
}
println!();
}
// output: he's ok
你可以从一个函数返回一个闭包:
fn make_tester(answer: String) -> impl Fn(&str) -> bool {
move |challenge| {
challenge == answer
}
}
fn main() {
// you can use `.into()` to perform conversions
// between various types, here `&'static str` and `String`
let test = make_tester("hunter2".into());
println!("{}", test("******"));
println!("{}", test("hunter2"));
}
你甚至可以移动函数某些参数的引用到这个函数返回的闭包里:
fn make_tester<'a>(answer: &'a str) -> impl Fn(&str) -> bool + 'a {
move |challenge| {
challenge == answer
}
}
fn main() {
let test = make_tester("hunter2");
println!("{}", test("*******"));
println!("{}", test("hunter2"));
}
// output:
// false
// true
或者使用省略的生命周期:
fn make_tester(answer: &str) -> impl Fn(&str) -> bool + '_ {
move |challenge| {
challenge == answer
}
}
到此为止,我们已经成功完成了差不多30分钟的阅读,现在应该已经能够阅读大部分在网上找到的Rust代码了。
写Rust的体验和阅读Rust是很不一样的。一方面,你不是在阅读一个问题的解决方案,你实际上实在解决它。另一方面,Rust编译期也会提供很多帮助。
对于上面所有故意制造的错误(例如这个代码不合法等等),rustc总是提供很好的错误信息和有用的建议。
如果有什么遗漏掉的提示信息,编译器团队是很乐意补充上去的。
如果想要更多的Rust资料,你可以看看下面这些:
- The Rust Book
- Rust By Example
- Read Rust
- This Week In Rust
我也会更新Rust的博客和推特,如果你喜欢这篇文章,你知道应该怎么做。
玩的开心!
原文链接
译者注:本文翻译自作者的一篇博客,该文提供了Rust中常见的很多语法,帮助大家理解这些语法的含义以便于能够更好地去阅读Rust的代码。翻译过程中可能会有很多地方由于译者水平有限无法准确表达,还请各位谅解!如果某些地方各位有什么好的建议,欢迎交流!
欢迎大家关注我的微信公众号:
