前言
在正式进入生命周期的介绍之前,这里先花一点篇幅谈一下基于借用的方法类型。
基于借用的方法类型(Method types based on borrowing)
借用规则规定了如何类型上定义固有方法(inherent methods),以及如何从特征中获取实例化方法。以下方法涉及如何接收实例,由最少限制到最多限制(least restricted to most restricted):
- &self方法:这些方法只对其成员具有不可变的访问权
- &mut self方法:这些方法以可变的方式借用self实例
- self方法:这些方法获得它所调用的实例的所有权,并且该类型之后不能被调用
对于用户定义的类型,同样的借用也适用于其字段成员。
注意一下:除非你故意写一个应该在结尾移动或删除self的方法,默认倾向于使用不可变的借用方法,也就是说,使用&self作为第一个参数。
生命周期概论
Rust的编译时涉及内存安全的第三个部分就是生命周期(lifetimes),自然涉及在代码中进行指定的相关语法和注释。在本节中,会对此逐一介绍。
通过初始化值来声明变量时,该变量具有一定的存在时间,超过该时间之后,就不能再使用了。在一般的编程用语中,变量的生命周期是指代码中的变量指向有效内存的区域。如果你用过C语言,应该很容易意识到变量的生存周期方面的问题:每次用malloc分配一个变量,应该有一个所有者(owner)来决定相关变量的生存期何时结束,何时释放内存。但问题是,它不是由C编译器强制执行的;相反,这成了程序员的责任。
对于在栈上分配的数据,可以很容易地通过查看代码来进行推理,确定一个变量是否是有效。但是,对于堆分配的值,这并不清楚。在Rust中,生命周期是一个具体的构造,而=并不是像在C中那样是一个概念性的概念。其所做的分析与程序员手工做的分析是一样的,也就是说,要检查值的范围和所引用的任何变量。
当讨论Rust中的生命周期时,一般只需要在有引用时,才回进行相关处理。Rust中的所有引用都附加了一个隐含的生命周期信息,其定义了引用相对于相关的值所有者的生命周期,以及引用的范围。大多数情况下,它是隐式的,编译器通过查看代码来计算变量的生命周期。但在某些情况下,编译器也会要求开发者指定意图。
到目前为止,在之前的代码示例中,我们学会了简单的处理引用和借用,当我们尝试编译下面的代码时,看看会会发生什么
// lifetime_basics.rs
struct SomeRef<T> {
part: &T
}
fn main() {
let a = SomeRef { part: &43 };
}
这段代码非常简单。这里有一个SomeRef结构,它存储了对泛型类型T的引用。在main中,我们创建了该结构的一个实例,用一个对i32的引用初始化part字段,即&43。结果编译未通过,报错如下:
在这种情况下,编译器要求我们添加一个叫做生命周期参数(lifetime parameter)的东西,其非常类似于泛型类型参数。泛型类型T表示任何类型,生命周期参数表示任何引用有效的区域或范围,当代码在借用检查器(borrow checker)中进行分析时,用来为编译器提供相应空间的实际使用信息。
应该说,生命周期纯粹是一个编译时的结构,它帮助编译器确定引用在作用域中可以使用到什么程度,并确保遵循借用规则;可以跟踪引用的来源,以及是否比借用的值存在更久。在Rust中,生命周期确保了引用不能超过它所指向的值。固然,生命周期不是开发人员会经常用到的东西,但是编译器可以使用它来判断引用的有效性。
结语
以上实例代码提及了生命周期参数(lifetime parameter),而这就是下篇的开篇内容。
主要参考和建议读者进一步阅读的文献
https://doc.rust-lang.org/book
深入浅出 Rust,2018,范长春
Rust编程之道,2019, 张汉东
The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger
Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger
Beginning Rust ,2018,Carlo Milanesi
Rust Cookbook,2017,Vigneshwer Dhinakaran