引用与借用
fn main() {
let s1 = String::from("abc");
let l = f1(&s1);
println!("s1 = {}, len = {}", s1, l);
}
fn f1(s: &String) -> usize {
s.len()
}
这里将s1
传给了 f1
函数,使用的是&s1
,f1
定义的形参类型是&String
。这些&
符号就是引用,它们允许你使用值但不获取所有权。
&s
语法 让我们创建了一个指向s1
的引用,但是不拥有它。因为不拥有这个值,在离开作用域时也不需要drop
特殊操作。
我们将获取引用作为函数参数称为借用。正如变量默认是不可边的,应用也是一样。
fn f1(s: &String) -> usize {
s.push_str("hello"); // 这里将发生错误,尝试修改不可变的借用的值
s.len();
}
与运算符
&
引用相反的操作是*
解引用
可变引用
fn main() {
let mut s = String:from("hello");
change(&mut s);
}
fn change(s: &mut String) {
s.push_str(", world");
}
首选将s
改为 mut
的,然后必须创建一个可变引用&mut s
和接受一个可变引用s: &mut String
。
不过可边引用有很大的限制,在特定作用域的特定数据有且只有一个可变引用。
let mut s1 = String::from("hello");
let s2 = &mut s1;
let s3 = &mut s1;
println!("{}, {}", s2, s3);
这里将报错,这个限制的好处是Rust
在编译时就避免数据竞争。数据竞争类似于竞态条件。它由这三个条件造成:
- 两个或更多指针同时访问同一数据
- 至少有一个指针被用来写数据
- 没有同步数据访问的机制
数据竞争会导致未定义行为,难以在运行时追踪,且难以诊断和恢复。Rust
避免了这种情况发生,因为它甚至不会编译存在数据竞争的代码。
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 离开了作用域,可以创建一个新的引用
let r2 = &mut s;
类似的规则也存在于可变引用和不可变引用中。
let mut s = String::from("hello");
let r1 = &s; // 可以
let r2 = &s; // 可以
let r3 = &mut s; // 不行
Rust
不允许不可变引用和可变引用同时指向一个值,不可变引用不希望再读取值时被修改。多个不可变引用是可以的。
悬垂引用
在具有指针的语言中,很容易变量指向一个已释放的内存,而错误的产生一个 悬垂指针。尝试创建一个悬垂指针:
fn main() {
let s = f1();
}
fn f1() -> &String {
let s = String::from("hello"); // 创建字符串,分配内存
&s // 返回字符串的引用
} // s 离开作用域,释放内存
当s
离开作用域时,s
将被释放。尝试返回s
的引用,Rust
不允许这么做,这里将发生错误。
引用的规则
总结:
- 在任意给定时间,要么只能有一个可变应用,要么只能是多个不可变引用
- 引用必须总是有效
网友评论