首先,很容易说 baz 和 bin 的签名都不同于 foo。 这些是对 Person 的引用,而不是 Person 本身。 但是 baz vs bin 又如何呢? 它们是相同的还是不同的? 您可能倾向于遵循与 foo vs bar 相同的逻辑,并确定 mut 是函数的内部细节。 但这不是真的! 观察:
fn main() {
let mut alice = Person { name: String::from("Alice"), age: 30 };
baz(&alice); // this works
bin(&alice); // this fails
bin(&mut alice); // but this works
}
对 bin 的第一个调用将不能编译,因为 bin 需要一个可变的引用,但是我们提供了一个不可变的引用。 我们需要使用第二个版本的调用。 这不仅有句法上的区别,而且还有语义上的区别:我们使用了一个可变的引用,这意味着我们不能同时使用其他引用(请记住我们从第2课中借用的规则)。
这样做的结果是,我们可以通过三种不同的方式将值传递给类型级别的函数:
按值传递(move 语义) ,如 foo
传递不可变引用,如 baz
通过可变引用传递,比如 bin
另外,捕获到的参数变量可以是不可变或可变的。
可变与不可变按值传递
这是一个相对容易看到。 通过可变的值传递,我们还能获得什么额外的功能? 当然,有能够改变 value 的能力! 让我们看一下实现生日功能的两种不同方式,这可以使某人的年龄增加1。
#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn birthday_immutable(person: Person) -> Person {
Person {
name: person.name,
age: person.age + 1,
}
}
fn birthday_mutable(mut person: Person) -> Person {
person.age += 1;
person
}
fn main() {
let alice1 = Person { name: String::from("Alice"), age: 30 };
println!("Alice 1: {:?}", alice1);
let alice2 = birthday_immutable(alice1);
println!("Alice 2: {:?}", alice2);
let alice3 = birthday_mutable(alice2);
println!("Alice 3: {:?}", alice3);
}
以下是一些重要的要点:
_immutable 实现遵循一个更具功能性的习惯用法,通过解构原始Person 值来创建新 Person 值。 这在 Rust 中可以正常工作,但不是惯用语言,并且效率可能较低。
birthday_mutable 是人为的,丑陋的一团糟,但它证明了我们的观点。 在这里,我们有两个引用: person 和 replacement。 它们都是可变的引用,但是 person 是可变的变量。 我们做的第一件事就是 person = replacement; 。 这会更改 person 变量所指向的内容,并且根本不会修改引用所指向的原始值。 事实上,在编译这个文件时,我们会得到一个警告,我们从未使用传递给 person 的值:
warning: value passed to `person` is never read
注意,在此示例中,我们需要将 alice 和 bob 都标记为可变。 这是因为我们通过可变引用传递它们,这要求我们有能力对它们进行修改。 这不同于使用 move 语义的值传递,因为在我们的主函数中,我们可以直接观察更改传入引用的效果。
还要注意,我们也有一个 birthday_immutable_broken 版本。 您可能会从名称中猜到它无法编译。 如果它是一个不可变的变量,我们无法更改 person 指向的对象。
fn main() {
let nums = vec![1, 2, 3, 4, 5];
for i in 1..3 {
for j in nums {
println!("{},{}", i, j);
}
}
}
陷阱问题: 它不能编译!
error[E0382]: use of moved value: `nums`
--> main.rs:4:18
|
4 | for j in nums {
| ^^^^ value moved here in previous iteration of loop
|
= note: move occurs because `nums` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
error: aborting due to previous error
fn main() {
let msg: &str = "Hi!";
let say_hi: u32 = |msg| println!("{}", msg);
}
然后我们得到一个错误消息:
error[E0308]: mismatched types
--> main.rs:3:23
|
3 | let say_hi: u32 = |msg| println!("{}", msg);
| ^^^^^^^^^^^^^^^^^^^^^^^^^ expected u32, found closure
|
= note: expected type `u32`
found type `[closure@main.rs:3:23: 3:48]`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
[closure@main.rs:3:23: 3:48]
fn main() {
let msg: &str = "Hi!";
let say_hi: [closure@main.rs:3:23: 3:48] = |msg| println!("{}", msg);
}
但是编译器会把我们打倒:
error: expected one of `!`, `(`, `+`, `::`, `;`, `<`, or `]`, found `@`
--> main.rs:3:25
|
3 | let say_hi: [closure@main.rs:3:23: 3:48] = |msg| println!("{}", msg);
| ------ ^ expected one of 7 possible tokens here
| |
| while parsing the type for `say_hi`
error: aborting due to previous error
我们现在还有一个类型参数,名为 f,用于传入的闭包。 我们现在对 f 一无所知,但是我们打算用函数调用的方式来使用它。 如果我们编译这个,我们会得到:
error[E0618]: expected function, found `F`
--> main.rs:8:5
|
7 | fn call_with_hi<F>(f: F) {
| - `F` defined here
8 | f("Hi!");
| ^^^^^^^^ not a function
error: aborting due to previous error
For more information about this error, try `rustc --explain E0618`.
好吧,很公平: 编译器不知道 f 是一个函数。 现在该终于可以介绍编译的魔力了: Fn trait!
现在我们已经对 F 设置了一个约束,即它必须是一个函数,该函数接受一个类型为 &str 的参数,并返回一个单元值。 实际上,返回单元值是默认值,所以我们可以省略这一部分:
fn call_with_hi<F>(f: F)
where F: Fn(&str)
{
f("Hi!");
}
Fn 的另一个妙处在于,它不仅仅适用于闭包。 它也可以运行常规的 ol 方法。
练习4
将 message 改写为 main 之外的函数,并使上面的程序能够编译。
这有点无聊,因为say_message实际上不是闭包。 让我们对此进行一些更改。
fn main() {
let name = String::from("Alice");
let say_something = |msg: &str| println!("{}, {}", msg, name);
call_with_hi(say_something);
call_with_hi(say_something);
call_with_bye(say_something);
call_with_bye(say_something);
}
fn call_with_hi<F>(f: F)
where F: Fn(&str)
{
f("Hi");
}
fn call_with_bye<F>(f: F)
where F: Fn(&str)
{
f("Bye");
}
可变变量
还记得过去在网页上设置访问计数器的美好时光吗? 让我们重新创造那种美好的体验吧!
fn main() {
let mut count = 0;
for _ in 1..6 {
count += 1;
println!("You are visitor #{}", count);
}
}
这是可行的,但是太无聊了! 让我们用一个结尾来让它更有趣些。
fn main() {
let mut count = 0;
let visit = || {
count += 1;
println!("You are visitor #{}", count);
};
for _ in 1..6 {
visit();
}
}
编译器不同意这种说法:
error[E0596]: cannot borrow immutable local variable `visit` as mutable
--> main.rs:9:9
|
3 | let visit = || {
| ----- help: make this binding mutable: `mut visit`
...
9 | visit();
| ^^^^^ cannot borrow mutably
error: aborting due to previous error
For more information about this error, try `rustc --explain E0596`.
error[E0596]: cannot borrow immutable argument `f` as mutable
--> main.rs:16:9
|
11 | fn call_five_times<F>(f: F)
| - help: make this binding mutable: `mut f`
...
16 | f();
| ^ cannot borrow mutably
error: aborting due to previous error
For more information about this error, try `rustc --explain E0596`.
fn main() {
let name = String::from("Alice");
let welcome = || {
let mut name = name;
name += " and Bob";
println!("Welcome, {}", name);
};
call_five_times(welcome);
}
fn call_five_times<F>(f: F)
where
F: Fn(),
{
for _ in 1..6 {
f();
}
}
我们得到了一个全新的错误消息,这次是引用 FnOnce:
error[E0525]: expected a closure that implements the `Fn` trait, but this closure only implements `FnOnce`
--> main.rs:4:19
|
4 | let welcome = || {
| ^^ this closure implements `FnOnce`, not `Fn`
5 | let mut name = name;
| ---- closure is `FnOnce` because it moves the variable `name` out of its environment
...
10 | call_five_times(welcome);
| --------------- the requirement to implement `Fn` derives from here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0525`.
用 FnOnce() 替换 Fn() 应该可以解决编译问题,对吧? 错!
error[E0382]: use of moved value: `f`
--> main.rs:18:9
|
18 | f();
| ^ value moved here in previous iteration of loop
|
= note: move occurs because `f` has type `F`, which does not implement the `Copy` trait
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
fn main() {
let name = String::from("Alice");
let welcome = || {
let mut name = name;
name += " and Bob";
println!("Welcome, {}", name);
};
call_once(welcome);
}
fn call_once<F>(f: F)
where
F: FnOnce(),
{
f();
}
fn main() {
// owned by main
let name_outer = String::from("Alice");
let say_hi = || {
// use by reference
let name_inner = &name_outer;
println!("Hello, {}", name_inner);
};
// main still owns name_outer, this is fine
println!("Using name from main: {}", name_outer); // success
// but name_inner lives on, in say_hi!
say_hi(); // success
say_hi(); // success
}
但是,如果我们稍微改变一下,使 name_outer 超出say_hi 的范围,一切都会崩溃!
fn main() {
let say_hi = { // forcing the creation of a smaller scope
// owned by the smaller scope
let name_outer = String::from("Alice");
// doesn't work, closure outlives captured values
|| {
// use by reference
let name_inner = &name_outer;
println!("Hello, {}", name_inner);
}
};
// syntactically invalid, name_outer isn't in this scope
//println!("Using name from main: {}", name_outer); // error!
say_hi();
say_hi();
}
fn main() {
let mut name = String::from("Alice");
let mut say_hi = || {
name += " and Bob";
println!("Hello, {}", name);
};
//call_fn(say_hi);
call_fn_mut(&mut say_hi);
call_fn_once(&mut say_hi);
}
我们还可以通过让 name 比闭包存在的时间更长来避免捕获。
// bad!
fn main() {
let mut name = String::from("Alice");
let mut say_hi = || {
name += " and Bob";
println!("Hello, {}", name);
};
//call_fn(say_hi);
call_fn_mut(&mut say_hi);
call_fn_once(&mut say_hi);
println!("And now name is: {}", name);
}
添加 println!最后,引用 name 的字段无效,因为 say_hi 仍在范围内。 这取决于词汇生命周期。 您可以通过在源代码顶部添加#![feature (nll)]来打开(撰写本文时)实验性功能非词汇性生存期。 或者,您可以显式使用花括号来表示闭包的范围:
fn main() {
let mut name = String::from("Alice");
{
let mut say_hi = || {
name += " and Bob";
println!("Hello, {}", name);
};
//call_fn(say_hi);
call_fn_mut(&mut say_hi);
call_fn_once(&mut say_hi);
}
println!("And now name is: {}", name);
}
您还可以(也许有些明显)以多种不同的方式使用一个值:
fn main() {
let mut name = String::from("Alice");
let mut say_hi = || {
println!("Hello, {}", name); // use by ref
name += " and Bob"; // use by mut ref
std::mem::drop(name); // use by value
};
//call_fn(say_hi);
//call_fn_mut(say_hi);
call_fn_once(say_hi);
}
我们将在不进行任何错误处理的情况下进行第一次尝试。 相反,我们将在 Result 值上使用 unwrap(),导致我们的程序恐慌! 如果出了什么差错。 我们稍后再清理。
let mut file = std::fs::File::create("mylog.txt");
file.write_all(b"I was clicked.\n");
在解决了下面的练习6之后,您将看到这个错误消息:
error[E0599]: no method named `write_all` found for type `std::fs::File` in the current scope
--> src/main.rs:27:14
|
27 | file.write_all(b"I was clicked.\n");
| ^^^^^^^^^
|
= help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope, perhaps add a `use` for it:
|
3 | use std::io::Write;
让我们将 create 调用移到闭包定义之外。 在 main 函数体中打开文件,闭包可以捕获对该文件的可变引用,这样一切都会顺利进行。
不幸的是,编译器真的不喜欢这样:
error[E0596]: cannot borrow `file` as mutable, as it is a captured variable in a `Fn` closure
--> src/main.rs:28:9
|
28 | file.write_all(b"I was clicked.\n");
| ^^^^ cannot borrow as mutable
|
help: consider changing this to accept closures that implement `FnMut`
--> src/main.rs:26:28
|
26 | button.connect_clicked(|_| {
| ____________________________^
27 | | use std::io::Write;
28 | | file.write_all(b"I was clicked.\n");
29 | | });
| |_____^
error[E0597]: `file` does not live long enough
--> src/main.rs:28:9
|
26 | button.connect_clicked(|_| {
| --- value captured here
27 | use std::io::Write;
28 | file.write_all(b"I was clicked.\n");
| ^^^^ borrowed value does not live long enough
...
32 | }
| - `file` dropped here while still borrowed
|
= note: borrowed value must be valid for the static lifetime...
error: aborting due to 2 previous errors
Some errors occurred: E0596, E0597.
For more information about an error, try `rustc --explain E0596`.
error: Could not compile `clicky`.
问题在于我们的文件没有 ‘static 生命周期。 它是在 main 函数中创建的,保留在 main 函数中,并且仅与 main 函数生命周期一样。 您可能会争辩说,main 函数贯穿程序的整个过程,但这并非完全正确。 在上面的示例中,调用 drop 时,按钮将失效文件(因为以FILO顺序执行 drop)。 如果某个按钮的 drop 按钮决定再次调用 click 回调,则说明内存不安全。
这意味着您可以从 Rc <T> 获取对 T 的引用。 由于方法调用语法会自动获取引用,因此一切正常。 真好。
好吧,几乎所有东西:
error[E0596]: cannot borrow data in a `&` reference as mutable
--> src/main.rs:32:9
|
32 | file.write_all(b"I was clicked.\n");
| ^^^^ cannot borrow as mutable
无论如何,添加 use std::cell::RefCell ;,然后将 RefCell 包裹原始File:
let file = std::fs::File::create("mylog.txt").unwrap();
let file = RefCell::new(file);
现在,我们的代码将无法进行编译并有一条不同的错误消息:
error[E0599]: no method named `write_all` found for type `std::cell::RefCell<std::fs::File>` in the current scope
--> src/main.rs:29:14
|
29 | file.write_all(b"I was clicked.\n").unwrap();
|
use std::thread::sleep;
use std::time::Duration;
fn main() {
let mut msg: String = String::from("Fearless");
for _ in 1..11 {
msg.push('!');
println!("{}", msg);
sleep(Duration::new(1, 0));
}
}
我们甚至可以在闭包中包装 msg.push 和 println! ,以得到更接近调用 spawn:
use std::thread::sleep;
use std::time::Duration;
fn main() {
let mut msg: String = String::from("Fearless");
for _ in 1..11 {
let inner = || {
msg.push('!');
println!("{}", msg);
};
inner();
sleep(Duration::new(1, 0));
}
}
这给了我们一个错误信息:
error[E0596]: cannot borrow immutable local variable `inner` as mutable
继续修复这个问题,并编译这个代码。
引入spawn
引入 spawn 的最简单方法是将 inner() 调用替换为 spawn(inner):
use std::thread::sleep;
和
use std::thread::{sleep, spawn};
然后添加 spawn 调用,我们会得到错误消息:
error[E0373]: closure may outlive the current function, but it borrows `msg`, which is owned by the current function
--> main.rs:7:25
|
7 | let mut inner = || {
| ^^ may outlive borrowed value `msg`
8 | msg.push('!');
| --- `msg` is borrowed here
help: to force the closure to take ownership of `msg` (and any other referenced variables), use the `move` keyword
|
7 | let mut inner = move || {
| ^^^^^^^
error: aborting due to previous error
error[E0382]: capture of moved value: `msg`
--> main.rs:8:13
|
7 | let mut inner = move || {
| ------- value moved (into closure) here
8 | msg.push('!');
| ^^^ value captured here after move
|
= note: move occurs because `msg` has type `std::string::String`, which does not implement the `Copy` trait
use std::thread::{sleep, spawn};
use std::time::Duration;
use std::rc::Rc;
fn main() {
let msg = Rc::new(String::from("Fearless"));
for _ in 1..11 {
let mut msg = msg.clone();
let mut inner = move || {
msg.push('!');
println!("{}", msg);
};
spawn(inner);
sleep(Duration::new(1, 0));
}
}
好吧,这是一个新问题:
error[E0277]: `std::rc::Rc<std::string::String>` cannot be sent between threads safely
--> main.rs:13:9
|
13 | spawn(inner);
| ^^^^^ `std::rc::Rc<std::string::String>` cannot be sent between threads safely
|
use std::thread::{sleep, spawn};
use std::time::Duration;
use std::sync::Arc;
use std::cell::RefCell;
fn main() {
let msg = Arc::new(RefCell::new(String::from("Fearless")));
for _ in 1..11 {
let mut msg = msg.clone();
let mut inner = move || {
let msg = msg.borrow_mut();
msg.push('!');
println!("{}", msg);
};
spawn(inner);
sleep(Duration::new(1, 0));
}
}
更安全的并发:
error[E0277]: `std::cell::RefCell<std::string::String>` cannot be shared between threads safely
--> main.rs:15:9
|
15 | spawn(inner);
| ^^^^^ `std::cell::RefCell<std::string::String>` cannot be shared between threads safely
|
= help: the trait `std::marker::Sync` is not implemented for `std::cell::RefCell<std::string::String>`
= note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::cell::RefCell<std::string::String>>`
= note: required because it appears within the type `[closure@main.rs:10:25: 14:10 msg:std::sync::Arc<std::cell::RefCell<std::string::String>>]`
= note: required by `std::thread::spawn`
use std::thread::{sleep, spawn};
use std::time::Duration;
use std::sync::{Arc, Mutex};
fn main() {
let msg = Arc::new(Mutex::new(String::from("Fearless")));
for _ in 1..11 {
let mut msg = msg.clone();
let mut inner = move || {
let msg = msg.lock();
msg.push('!');
println!("{}", msg);
};
spawn(inner);
sleep(Duration::new(1, 0));
}
}
我们获得了一个错误:
error[E0599]: no method named `push` found for type `std::result::Result<std::sync::MutexGuard<'_, std::string::String>, std::sync::PoisonError<std::sync::MutexGuard<'_, std::string::String>>>` in the current scope
大多数互斥对象只是简单对 Result 使用 unwrap(),在线程之间传播恐慌,以确保不会发现可能无效的不变式