素数|连续 3 年最受欢迎:Rust,香!( 三 )


error[E0106]: missing lifetime specifier --> dangle.rs:5:16 |5 | fn dangle() -> &String; { | ^ expected lifetime parameter | = help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from = help: consider giving it a 'static lifetime
当编译这段代码时会得到一个错误:
error: `x` does not live long enough |6 | r = &x; | - borrow occurs here7 | } | ^ `x` dropped here while still borrowed...10 | } | - borrowed value needs to live until here
编译错误显示:变量 x 并没有 “活的足够久”,那么Rust是如何判断的呢?
编译器的这一部分叫做 借用检查器(borrow checker),它比较作用域来确保所有的借用都是有效的。如下:r 和 x 的生命周期注解,分别叫做 'a 和 'b:
{ let r; // -------+-- 'a // | { // | let x = 5; // -+-----+-- 'b r = &x; // | | } // -+ | // | println!("r: {}", r); // |} // -------+
我们将 r 的生命周期标记为 'a 并将 x 的生命周期标记为 'b。如你所见,内部的 'b 块要比外部的生命周期 'a 小得多。在编译时,Rust 比较这两个生命周期的大小,并发现 r 拥有生命周期 'a,不过它引用了一个拥有生命周期 'b 的对象。程序被拒绝编译,因为生命周期 'b 比生命周期 'a 要小:被引用的对象比它的引用者存在的时间更短。
关于借用生命周期检查,Rust还有一套复杂的生命周期标记规则,使Rust能在编译时就能发现可能存在的悬垂引用,具体链接见[5]。
4多线程安全保证
内存破坏很多情况下是由数据竞争(data race)所引起,它可由这三个行为造成:
两个或更多指针同时访问同一数据。
至少有一个这样的指针被用来写入数据。
不存在同步数据访问的机制。
那么在多线程环境下,Rust是如何避免数据竞争的?
先从一个简单的例子说起,尝试在另一个线程使用主线程创建的 vector:
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); handle.join().unwrap();}
闭包使用了 v,所以闭包会捕获 v 并使其成为闭包环境的一部分。因为 thread::spawn 在一个新线程中运行这个闭包,所以可以在新线程中访问 v。然而当编译这个例子时,会得到如下错误:
error[E0373]: closure may outlive the current function, but it borrows `v`,which is owned by the current function --> src/main.rs:6:32 |6 | let handle = thread::spawn(|| { | ^^ may outlive borrowed value `v`7 | println!("Here's a vector: {:?}", v); | - `v` is borrowed here |help: to force the closure to take ownership of `v` (and any other referencedvariables), use the `move` keyword |6 | let handle = thread::spawn(move || { | ^^^^^^^
Rust 会“推断”如何捕获 v,因为 println! 只需要 v 的引用,闭包尝试借用 v。然而这有一个问题:Rust 不知道这个新建线程会执行多久,所以无法知晓 v 的引用是否一直有效。所以编译器提示:
closure may outlive the current function, but it borrows `v` 。
下面展示了一个 v 的引用很有可能不再有效的场景:
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(|| { println!("Here's a vector: {:?}", v); }); drop(v); // 强制释放变量v handle.join().unwrap();}
为了修复示上面的编译错误,我们可以听取编译器的建议:
help: to force the closure to take ownership of `v` (and any other referencedvariables), use the `move` keyword |6 | let handle = thread::spawn(move || {
接下来是正确的写法:
use std::thread;fn main() { let v = vec![1, 2, 3]; let handle = thread::spawn(move || { //使用 move 关键字强制获取它使用的值的所有权,接下来就可以正常使用v了 println!("Here's a vector: {:?}", v); }); handle.join().unwrap();}
从上面简单例子中可以看出多线程间参数传递时,编译器会严格检查参数的生命周期,确保参数的有效性和可能存在的数据竞争。
大家注意到没有,上面的例子虽然能正确编译通过,但是有个问题,变量v的所有权已经转移到子线程中,main函数已经无法访问v,如何让main再次拥有v呢?如果用C++或者Golang等语言,你可以有很多种选择,比如全局变量,指针,引用之类的,但是Rust没有给你过多的选择,在Rust中,为了安全性考虑,全局变量为只读不允许修改,并且引用不能直接在多线程间传递。Rust 中一个实现消息传递并发的主要工具是 通道(channel),这种做法时借鉴了Golang的通道,用法类似。
示例:
use std::thread;use std::sync::mpsc;fn main() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); println!("Got: {}", received);}


推荐阅读