这个用的太多了,比如我们定义一个人:
struct Person { name: &str } impl Person { fn new(name: &str) -> Person { Person { name: name} } } fn main() { let nobody: Person = Person::new("nobody"); }
这么简单常用的代码,rust编译器也不放过,会报错:没有生命周期的参数。 Missing lifetime specifier。
为什么会这样呢? 其实这是rust编译器在和编程者讨价还价:如果你给Person这个结构体的属性name一个真正的String对象,Person的实例化对象就会拥有这个String的所有权,从而我可以保证这个String和Person的实例一起被销毁,在Person实例的生命周期结束之后。
但是现在,你给我的是一个引用,从而Person的实例nobody并不拥有这个引用的所有权,那么我怎么知道这个引用正好和Person一起被销毁呢?如果在引用之前就被销毁了,那么销毁之后Person结构体如果想用self.name 我找谁呢?
这就是&str不太方便的一个地方,因为所有权并不属于赋值的属性,所以需要手工的告诉编译器它的生命周期。下面这个式子,是可以通过编译的:
““
struct Person<‘a> {
name: &’a str
}
impl<‘a> Person<‘a> {
fn new(name: &’a str) -> Person<‘a> {
Person { name: name}
}
}
fn main() {
let nobody: Person = Person::new(“nobody”);println!(“{:?}”, nobody.name);
}
““
这个会正确的打印 “nobody” 到终端上。
‘生命周期的标识符, a是代号,你可以写任何东西,比如’person, ‘name, 只要不是’static就可以,因为这个是系统保留的,用来表示全局不销毁的静态变量。Rust的惯例是a,如果有两个不同的周期就是a和b,以此类推。是不是挺有谭浩强的即视感?我也觉得有点,不过既然这是官方文档的惯例,我们还是遵从为好。
生命周期这么插入代表什么意思呢?其实生命周期的语法和泛型的语法蛮像,本质上是一个声明,告诉编译器,Person这个struct的实例将和name里面的&str 同周期,共命运(因为他们的生命周期都是’a, 所以是一样的)。编译器得到了这个 保证,就知道在何时可以销毁Person的实例了。如果有两个生命周期,那就 ‘a:’b 表示 a至少活得和b一样长。
注意,在Rust里面,生命周期只是一个annotation!!具体细节以后再说
下面我们玩个花活,结束今天的笔记,注释都在代码里:
“““`
struct Person<‘a> {
name: &’a str // 表示name这个属性的内容和Person同始终。
}
struct Student<‘a, ‘b:’a> { // 表示student里面有俩字符串,name和grade,name这个变量至少活得和grade一样长。
name: &’b str,
grade: &’a str
}
impl<‘a, ‘b> Student <‘a, ‘b> { // 来给student这个结构添加函数,函数里面有两个生命周期的参数。fn new(person: &’b Person, grade: &’a str) -> Student<‘a, ‘b> {
/ 用person的实例来创建student,因为是引用,所以也要告诉编译器生命周期的事情,并且我们一般先创建person,然后再添加grade,所以grade的生命周期不大于person的。 /
Student { //返回一个student实例
name: person.name,
grade: grade
}
}
}
impl<‘a, ‘b: ‘a> Person<‘a> { // 给person结构体添加函数
fn new(name: &’a str) -> Person<‘a> {
Person { name: name}
}
fn change_name(&’a mut self, new_name: &’b str) { //注意这里!&’a mut 是可变借用self.name = new_name
}
}
fn main() {
let mut nobody: Person = Person::new(“nobody”);{ //去掉这两个括号,马上报错!
let student: Student = Student::new(&nobody, “A”);
println!(“{:?}”, (student.name, student.grade));
} //去掉这两个括号,马上报错!nobody.change_name(“somebody”);
/* 去掉大括号之后,编译器会说,student已经不可变的借用了nobody的name,没法可变借用,因为我们的student实例一直牢牢的借着 nobody的name不还,生命周期还没有完成但是,加了大括号,student的生命周期已经完成,已经被销毁,但是因为name的生命周期并没有结束,所以还可以change_name.
*/
}
““““““`