生命周期了啥(一)

203 阅读3分钟

        几乎毫无疑问,rust的生命周期是这么语言最最难理解的部分,甚至没有之一,并不是说其他语言就没有生命周期的概念,而是rust的代码需要标记生命周期,无法理解生命周期,就无法进阶。要搞清楚生命周期,首先得明白生命周期标记本质上跟泛型是类似的标记。 

struct Wrapper<T>{
    data:T
}

 这里的T标识的是T是一个任意的type,即结构体Wrapper的data字段可以存任意的类型,任意类型的泛型意义不大,一般还需要在T后面加上限定,即Trait bound,如T:Display,意义为T类型需要实现Display。再比如一个结构体 

struct Point<T>{      
    x:T,
    y:T 
} 

这时候,x和y都必须是同一个类型,如同为i32,不能x为i32,y为f32,这样编译是无法通过的。这时候有一个结构体 

struct Ref<'any>{  
    data:&'any str 
}

这里'any代表的就是一个生命周期的标识,类似上面的泛型,他代表的是任意的生命周期,它在这里的意思是:编译器,我这里存了一个引用,请帮我检查引用的合法性。在rust代码里经常写成'a,其实你写成什么并没有规定,只是惯例用法,建议写有含义的单词,以便于自己识别。既然代表的是任意生命周期,那么他到底有什么用呢?

  1. rust的生命周期标记是编译期处理的,与运行时无关,也就是说这个标记是编译器要求你解释清楚你的行为,以便于静态分析
  2. 多数情况下,编译器会自动运行生命周期省略规则,当规则与实际代码冲突时,才会要求你添加显式的生命周期标记

上文结构体Ref从另外一个角度可以理解成:data字段仅当被引用的值在存活期内才合法。这?好像越来越看不懂了。对于rust来说,每一个值在同一时刻,都只有一个合法的所有人,这一机制被称为所有权,也就是说值的生命周期,是编译器就能确定下来的,这也是rust为什么能不依赖GC(垃圾回收器器),也能实现自动内存管理的原因。rust编译器,会自动在作用域的结尾插入清理内存的代码,也就是自动执行Drop,得益于确定性析构,自动内存管理就这样魔法般实现了。

讲到这里,其实生命周期的概念还是很模糊的,没关系,这时候数学里的集合派生用场了(不用怕,咱不讲公式)。假设我写了如下代码:

那么foo的生命周期可以记作[2,3],从第二行开始,到第三行结束。然后我们再加长代码:

这时候foo的生命周期延长到了[2,4],而新定义的bar生命周期为[3,4],他们的生命周期在第四行重叠了。用这种方法,可以清晰知道值的存活期。然后我们来理解一下值的引用:

这时候会提示编译不通过,因为foos的生命周期是[2,3],因为手动提前释放了foos,而引用了foos的值w生命周期为[4,5],两者并不相交,删除drop(foos)这行代码,即可编译正常。这里被引用的值的生命周期需大于或等于引用值(即生命周期需要相交),才是合法的,一旦引用的生命周期大于原来的值,那就是悬垂指针了,自然不合法。生命周期的标记意义,就在于编译器强制用户要解释清楚自己的意图,以确保代码的准确性。