首先,很容易说 baz 和 bin 的签名都不同于 foo。 这些是对 Person 的引用,而不是 Person 本身。 但是 baz vs bin 又如何呢? 它们是相同的还是不同的? 您可能倾向于遵循与 foo vs bar 相同的逻辑,并确定 mut 是函数的内部细节。 但这不是真的! 观察:
fnmain() {letmut alice =Person { name:String::from("Alice"), age:30 };baz(&alice); // this worksbin(&alice); // this failsbin(&mut alice); // but this works}
对 bin 的第一个调用将不能编译,因为 bin 需要一个可变的引用,但是我们提供了一个不可变的引用。 我们需要使用第二个版本的调用。 这不仅有句法上的区别,而且还有语义上的区别:我们使用了一个可变的引用,这意味着我们不能同时使用其他引用(请记住我们从第2课中借用的规则)。
这样做的结果是,我们可以通过三种不同的方式将值传递给类型级别的函数:
按值传递(move 语义) ,如 foo
传递不可变引用,如 baz
通过可变引用传递,比如 bin
另外,捕获到的参数变量可以是不可变或可变的。
可变与不可变按值传递
这是一个相对容易看到。 通过可变的值传递,我们还能获得什么额外的功能? 当然,有能够改变 value 的能力! 让我们看一下实现生日功能的两种不同方式,这可以使某人的年龄增加1。
birthday_mutable 是人为的,丑陋的一团糟,但它证明了我们的观点。 在这里,我们有两个引用: person 和 replacement。 它们都是可变的引用,但是 person 是可变的变量。 我们做的第一件事就是 person = replacement; 。 这会更改 person 变量所指向的内容,并且根本不会修改引用所指向的原始值。 事实上,在编译这个文件时,我们会得到一个警告,我们从未使用传递给 person 的值:
warning: value passed to `person` is never read
注意,在此示例中,我们需要将 alice 和 bob 都标记为可变。 这是因为我们通过可变引用传递它们,这要求我们有能力对它们进行修改。 这不同于使用 move 语义的值传递,因为在我们的主函数中,我们可以直接观察更改传入引用的效果。
还要注意,我们也有一个 birthday_immutable_broken 版本。 您可能会从名称中猜到它无法编译。 如果它是一个不可变的变量,我们无法更改 person 指向的对象。
fnmain() {let nums =vec![1, 2, 3, 4, 5];for i in1..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` traiterror: aborting due to previous error
error[E0434]: can't capture dynamic environment in a fn item--> main.rs:4:24|4|println!("{}", msg);|^^^|= help:use the `|| { ... }` closure form insteaderror: aborting due to previous error
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 errorFor more information about this error, try `rustc --explain E0308`.[closure@main.rs:3:23:3:48]
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 typefor `say_hi`error: aborting due to previous error
我们现在还有一个类型参数,名为 f,用于传入的闭包。 我们现在对 f 一无所知,但是我们打算用函数调用的方式来使用它。 如果我们编译这个,我们会得到:
error[E0618]: expected function, found `F`--> main.rs:8:5|7|fncall_with_hi<F>(f:F) {|- `F` defined here8|f("Hi!");|^^^^^^^^ not a functionerror: aborting due to previous errorFor more information about this error, try `rustc --explain E0618`.
好吧,很公平: 编译器不知道 f 是一个函数。 现在该终于可以介绍编译的魔力了: Fn trait!
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 mutablyerror: aborting due to previous errorFor more information about this error, try `rustc --explain E0596`.
error[E0596]: cannot borrow immutable argument `f` as mutable--> main.rs:16:9|11|fncall_five_times<F>(f:F)|- help: make this binding mutable: `mut f`...16|f();|^ cannot borrow mutablyerror: aborting due to previous errorFor more information about this error, try `rustc --explain E0596`.
fnmain() {let name =String::from("Alice");let welcome =|| {letmut name = name; name +=" and Bob";println!("Welcome, {}", name); };call_five_times(welcome);}fncall_five_times<F>(f:F)whereF:Fn(),{for _ in1..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|letmut 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 hereerror: aborting due to previous errorFor 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` traiterror: aborting due to previous errorFor 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);
}
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
Refcell 就是为解决这个问题而设计的。 我不打算详细解释它,因为用于 std::cell 的 API 文档比我做得更好。 我建议你现在就去阅读那篇介绍文章,然后再回来研究这段代码,然后再去阅读文档。 就我个人而言,我不得不把这个解释读上4到5遍,然后多思考一些代码,最后才能正确地理解。
无论如何,添加 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