Appearance
12.3 借用规则
关于借用指针,有以下几个规则:
借用指针不能超过值的生命周期 (expected lifetime parameter,编译器提示缺少生命周期参数,生命周期是我们后续学习的内容)
&mut
型借用只能指向本身具有 mut 修饰的变量,对于只读变量,不可以有&mut
型借用。&mut
型借用指针存在的时候,被借用的变量本身会处于“冻结”状态,否则会出现针对同一个值的两个可读写入口的存在。如果只有
&
型借用指针,那么能同时存在多个;如果存在&mut
型借用指针,那么只能存在一个;如果同时有其他的&
或者&mut
型借用指针存在,那么会出现编译错误。
总结: 一个值在任意给定的时间,要么只能有一个活跃的可变引用,要么只能有多个不可变引用。
借用指针只能临时地拥有对这个变量读或写的权限,没有义务管理这个变量的生命周期。
示例如下:
rust
// 这里的参数采用的“引用传递”,意味着实参本身并未丢失对内存的管理权
fn borrow_semantics(v : &Vec<i32>) {
// 打印参数占用空间的大小,在 64 位系统上,结果为 8,表明该指针与普通裸指针的内部表示方法相同
println!("size of param: {}", std::mem::size_of::<&Vec<i32>>());
for item in v {
print!("{} ", item);
}
println!("");
}
// 这里的参数采用的“值传递”,而 Vec 没有实现 Copy trait,意味着它将执行 move 语义
fn move_semantics(v : Vec<i32>) {
// 打印参数占用空间的大小,结果为 24,表明实参中栈上分配的内存空间复制到了函数的形参中
println!("size of param: {}", std::mem::size_of::<Vec<i32>>());
for item in v {
print!("{} ", item);
}
println!("");
}
fn main() {
let array = vec![1, 2, 3];
// 需要注意的是,如果使用引用传递,不仅在函数声明的地方需要使用&标记
// 函数调用的地方同样需要使用&标记,否则会出现语法错误
// 这样设计主要是为了显眼,不用去阅读该函数的签名就知道这个函数调用的时候发生了什么
// 而小数点方式的成员函数调用,对于 self 参数,会“自动转换”,不必显式借用,这里有个区别
borrow_semantics(&array);
// 在使用引用传递给上面的函数后,array 本身依然有效,我们还能在下面的函数中使用
move_semantics(array);
// 在使用 move 语义传递后,array 在这个函数调用后,它的生命周期已经完结
}
在这里给大家提个醒:一般情况下,函数参数使用引用传递的时候,不仅在函数声明这里要写上类型参数,在函数调用这里也要显式地使用引用运算符。 但是,有一个例外,那就是当参数为self
&self
&mut self
等时,若使用小数点语法调用成员方法,在函数调用这里不能显式写出借用运算符。 以常见的 String 类型来举例:
rust
fn main() {
// 创建了一个可变的 String 类型实例
let mut x : String = "hello".into();
// 调用 len(&self) -> usize 函数。self 的类型是 &Self
// x.len() 等同于 String::len(&x)
println!("length of String {}", x.len());
// 调用 fn push(&mut self, ch: char) 函数。self 的类型是 &mut Self,因此它有权对字符串做修改
// x.push('!') 等同于 String::push(&mut x, '!')
x.push('!');
println!("length of String {}", x.len());
// 调用 fn into_bytes(self) -> Vec<u8> 函数。注意 self 的类型,此处发生了所有权转移
// x.into_bytes() 等同于 String::into_bytes(x)
let v = x.into_bytes();
// 再次调用 len(),编译失败,因为此处已经超过了 x 的生命周期
//println!("length of String {}", x.len());
}
在这个示例中,所有的函数调用都是同样的语法,比如x.len()
、x.push('!')
、x.into_bytes()
等, 但它们背后对 self 参数的传递类型完全不同,因此也就出现了不同的语义。这是需要提醒大家注意的地方。 当然,如果我们使用统一的完全限定语法,那么所有的参数传递类型在调用端都是显式写出来的。
任何借用指针的存在,都会导致原来的变量被“冻结”(Frozen)。示例如下:
rust
fn main() {
let mut x = 1_i32;
let p = &mut x;
x = 2;
println!("value of pointed : {}", p);
}
编译结果为:
txt
error[E0506]: cannot assign to `x` because it is borrowed
因为p
的存在,此时对x
的改变被认为是非法的。至于为什么会有这样的规定,请参考下一章。