引用
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {//函数签名中&代表这是一个引用参数
s.len()
}
上面的代码中,calculate_length(s: &String)使用了String的引用传递参数,而没有直接转移值的所有权。在调用函数时也需要传递参数的引用。
这些&代表的就是引用语义,它们允许你在不获取所有权的情况下使用值。
注意: 与&操作相反的是 * 操作,* 代表解引用
借用
上面这种通过引用传递参数给函数的方法也被称为借用,借用来的值默认不能被修改,否则会报错,编译无法通过:
//错误代码
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
可变引用
我们可用过一些调整来使借用来的值可变:可变引用。
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
我们需要做三个步骤,写三个mut:
- 将变量设置为可变 mut
- 将引用实参设置为可变 &mut
- 将引用形参设置为可变 &mut
在可变引用的使用上有一个很大的限制: 在一个作用域中,同时只能存在一个可变引用。
以下为错误代码:
//错误代码
fn main() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
}
这种限制可以帮助我在编译时避免数据竞争。数据竞争和竞态条件十分类似,它会在指令满足以下条件时发生:
- 两个或以上的指针访问同一空间
- 至少一个指针在空间中写入数据
- 没有同步数据访问机制
rust中可以使用花括号来创造作用域,所以可以使两个可变引用在两个不同的作用域中:
fn main() {
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
}
对于引用还有一个限制: 不可以在有不可变引用时,使用可变引用。
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
println!("{}, {}, and {}", r1, r2, r3);
}
因为程序员大部分时候都不希望自己的只读值突然发生改变。
总结两个限制: 在同一作用域中,如果存在一个可变引用,则不能使用其他任何引用。
悬垂引用
悬垂引用比较类似野指针,指的是指向曾经存在某处内存地址,但该内存已经被释放甚至重新分配另作他用了。
在rust的设计中,能保证这个数据不会再引用被销毁前离开自己的作用域。(这句话挺绕的,我的理解就是引用指向的数据始终都是有效的,也一直是你指向的那个,不会改变)
如下是错误代码:
//错误代码
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
其实,这部分只需要把引用去掉就可以使用了。如果一定要用引用会涉及生命周期,这在后面讨论它。