08_rust的引用和借用
rust的引用和借用
fn main() {
let s1 = String::from("hello");
let len = test_func(&s1);
println!("{} {}", s1, len);
}
fn test_func(s: &String) -> usize {
s.len();
}
从这个例子开始,函数参数传递的时s1的引用&String,这样就不会转移所有权。
引用
& 符号表示引用,允许引用某些值而不取得所有权。
上述例子,参数s其实是指向s1的指针,通过s访问s1的内存,进而访问字符串。
fn main() {
let s1 = String::from("hello");
let len = test_func(&s1); // 创建了一个s1的引用,但不拥有s1,之后进入函数,s1的所有权不会被清理掉
// let len2 = test_func2(s1); // 作为对比,这种会转移s1的所有权,s1会被清理掉
println!("{} {}", s1, len);
}
// 参数s变量,因为未获得所有权,所以函数返回后,
// 退出了s的作用域,也不会清除所有权,这种情况称之为借用
fn test_func(s: &String) -> usize {
s.len();
}
fn test_func2(s: String) -> usize {
s.len();
}
借用
把引用作为函数参数的行为叫做借用。
如果想试图修改s的内容,则会报编译错误。
fn main() {
let s1 = String::from("hello");
let len = test_func(&s1);
println!("{} {}", s1, len);
}
fn test_func(s: &String) -> usize {
s.push_str("world"); // 编译报错:cannot borrow '*s' as mutable
s.len();
}
可见无法修改借用的东,和变量一样,引用默认也是不可变的。
可变引用
如果要修改,则应使用可变引用,加上mut关键字。如下
fn main() {
let mut s1 = String::from("hello");
let len = test_func(&mut s1);
println!("{} {}", s1, len);
}
fn test_func(s: &mut String) -> usize {
s.push_str("world"); // 编译通过,且对s1修改成果
s.len();
}
可变引用有个重要的限制:在特定作用域内,对某一块数据,只能有一个可变引用。
如下例子则会报错:
fn main() {
let mut s = String::from("hello");
let s1 = &mut s;
let s2 = &mut s; // 编译报错:cannot borrow 's' as mutable more than once at a time
}
这样做的好处是可在编译时防止数据竞争。
以下三种行为下会发生数据竞争
- 两个或多个指针同时访问同一个数据
- 至少有一个指针用于写入数据
- 没有用任何机制来同步对数据的访问
数据竞争问题在运行时很难排查。如果满足这三种情况,rust会在编译时报错,从根本上接近数据竞争的问题。
而在rust中,可通过创建新的作用域,来允许非同时的创建多个可变引用。
fn main() {
let mut s = String::from("hello");
{
let s1 = &mut s;
}
let s2 = &mut s; // 至此s1的作用域已经结束
}
另外,不可同时拥有一个可变引用和一个不可变引用。因为当可变引用把值改变后,不可变引用就失效了,产生语法二义。
多个不可变引用时可以的。
fn main() {
let mut s = String::from("hello");
let s1 = &s;
let s2 = &s;
let s2 = &mut s; // 编译报错:cannot borrow 's' as mutable because it is alse borrowed as immutable
}
悬空引用 Dangling References
跟悬空指针(Dangling Pointer)类似,一个指针引用了内存中的某个地址,但这内存可能已经释放或分配给其它地方使用。
在rust里,编译器可保证引用永远都不是悬空引用,如果引用了某些数据,编译器将保证引用离开作用域之前,数据不会离开作用域。
fn main() {
let r = dangle_test();
}
fn dangle_test() -> &String {//编译报错:missing lefetime specifier,避免悬空引用
let s = String::from("test");
&s;
}
引用规则
在任何给定的时刻,只能满足条件之一:
- 一个可变的引用
- 任意数量不可变的应用
引用必须一直有效。