生命周期 先看一个示例
1 2 3 4 5 6 7 8 fn main() { let v = vec! [1,2,3,4,5]; // --> v 的生命周期开始 { let center = v[2]; // --> center 的生命周期开始 println! ("{}", center); } // <-- center 的生命周期结束 println! ("{:? }", v); } // <-- v 的生命周期结束
借用 变量对其管理的内存拥有所有权。这个所有权不仅可以被转移(move),还可以被借用(borrow)。
借用指针的语法使用
&符号只读借用
&mut可读写借用
借用指针与普通指针的内部数据是一模一样的,唯一的区别是语义层面上的。它的作用是告诉编译器,它对指向的这块内存区域没有所有权。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 fn main() { let mut var = 0_i32; { let p1 = &mut var; // p1 指针本身不能被重新绑定,我们可以通过p1改变变量var的值 *p1 = 1; } { let temp = 2_i32; let mut p2 = &var; // 我们不能通过p2改变变量var的值,但p2指针本身指向的位置可 以被改变 p2 = &temp; } { let mut temp = 3_i32; let mut p3 = &mut var; // 我们既可以通过p3改变变量var的值,而且p3指针本身指向 的位置也可以改变 *p3 = 3; p3 = &mut temp; } }
借用指针在编译后,实际上就是一个普通的指针,它的意义只能在编译阶段的静态检查中体现。
借用规则
借用指针不能比它指向的变量存在的时间更长。
&mut型借用只能指向本身具有mut修饰的变量,对于只读变量,不可以有&mut型借用。
&mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态。
如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;如果同时有其他的&或者&mut型借用指针存在,那么会出现编译错误。
借用指针只能临时地拥有对这个变量读或写的权限,没有义务管理这个变量的生命周期。因此,借用指针的生命周期绝对不能大于它所引用的原来变量的生命周期,否则就是悬空指针,会导致内存不安全。示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 // 这里的参数采用的“引用传递”,意味着实参本身并未丢失对内存的管理权 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类型来举例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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()); }
任何借用指针的存在,都会导致原来的变量被“冻结”(Frozen)。示例如下:
1 2 3 4 5 6 fn main() { let mut x = 1_i32; let p = &mut x; x = 2; println! ("value of pointed : {}", p); }
编译结果为
1 error: cannot assign to `x` because it is borrowed
生命周期标记 函数的生命周期标记 1 2 3 4 5 6 7 8 9 10 11 12 13 14 01| struct T { 02| member: i32, 03| } 04| 05| fn test<'a>(arg: &'a T) -> &'a i32 06| { 07| &arg.member 08| } 09| 10| fn main() { 11| let t = T { member : 0 }; //----- 't -| 12| let x = test(&t); //-- 'x ---| | 13| println! ("{:? }", x); // | | 14| } //-- 'x ----- 't -
生命周期之间有重要的包含关系。如果生命周期’a比’b更长或相等,则记为’a :’b,意思是’a至少不会比’b短。对于借用指针类型来说,如果&’a是合法的,那么’b作为’a的一部分,&’b也一定是合法的。
‘static是一个特殊的生命周期,它代表的是这个程序从开始到结束的整个阶段,所以它比其他任何生命周期都长。这意味着,任意一个生命周期’a都满足’static :’a。
修改上述test函数,编译报错,因为’a和’b没有关系,没办法确定生命周期包含关系。
1 2 3 4 fn test<'a, 'b>(arg: &'a T) -> &'b i32 { &arg.member }
可以修改为
1 2 3 4 5 fn test<'a, 'b>(arg: &'a T) -> &'b i32 where 'a:'b { &arg.member }
编译器可以把&x和&y的生命周期都缩小到某个生命周期’a以内,且满足’x : ‘a, ‘y : ‘a。返回的selected变量具备’a生命周期,也并没有超过’x和’y的范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 fn select<'a>(arg1: &'a i32, arg2: &'a i32) -> &'a i32 { if *arg1 > *arg2 { arg1 } else { arg2 } } fn main() { let x = 1; let y = 2; let selected = select(&x, &y); println! ("{}", selected); }
类型的生命周期标记 1 2 3 struct Test<'a> { member: &'a str }
1 2 3 4 impl<'t> Test<'t> { fn test<'a>(&self, s: &'a str) { } }
如果在泛型约束中有where T: ‘a之类的条件,其意思是,类型T的所有生命周期参数必须大于等于’a。要特别说明的是,若是有where T: ‘static的约束,意思则是,类型T里面不包含任何指向短生命周期的借用指针,意思是要么完全不包含任何借用,要么可以有指向’static的借用指针。
省略生命周期标记 省略的生命周期被自动补全的规则
每个带生命周期参数的输入参数,每个对应不同的生命周期参数;
如果只有一个输入参数带生命周期参数,那么返回值的生命周期被指定为这个参数;
如果有多个输入参数带生命周期参数,但其中有&self、&mut self,那么返回值的生命周期被指定为这个参数;
以上都不满足,就不能自动补全返回值的生命周期参数。
1 2 3 4 fn get_str(s: &String) -> &str { println! ("call fn {}", s); "hello world" }
编译器会自动补全生命周期参数:
1 2 3 4 fn get_str<'a>(s: &'a String) -> &'a str { println! ("call fn {}", s); "hello world" }