問題描述

前些天群裡有人遇到了個rust生命週期編譯出錯的問題,幾個人討論了個大半天也沒個結果。 作為曾被rustc毒打的過來人,自然是立馬看出了問題所在,不過在群裡做了點簡單解釋,好像都沒聽懂理解。今天空下來了就寫篇文章對其作一個詳細的分析,方便其他遇到類似問題的人作個參考。 由於出現問題的原始碼比較長,不過生命週期的問題只需要看函式簽名和結構定義就夠了,為了大家好理解,我去掉不相干的結構和函式實現,簡化下大致是:

struct

Parser

{

// 。。。

}

pub

struct

ParseIter

<

‘a

>

{

parser

&

’a

mut

Parser

buf

&

‘a

u8

],

}

impl

Parser

{

pub

fn

parse

<

’a

>

&

‘a

mut

self

buf

&

’a

u8

])

->

Option

<&

‘a

u8

>

{

todo

()

}

pub

fn

iter

<

’a

>

&

‘a

mut

self

buf

&

’a

u8

])

->

ParseIter

<

‘a

>

{

ParseIter

{

parser

self

buf

}

}

}

定義了Parser和ParseIter結構,並試圖給ParseIter結構實現Iterator。

impl<’a> Iterator for ParseIter<‘a> {

type Item = &’a u8;

fn next(&mut self) -> Option {

return self。parser。parse(self。buf);

}

}

然後就在next方法報告如下錯誤:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements

——> src/lib。rs:24:22

|

24 | return self。parser。parse(self。buf);

| ^^^^^

ParseIter無法安全實現Iterator

這個問題的根本原因其實是ParseIter的結構定義中的parser欄位使用的是mut引用,導致其無法安全實現Iterator。假定其實現了Iterator,那麼看下面的程式碼:

let v1 = iter。next();

let v2 = iter。next();

在呼叫第二個next函式時,其可以對parser進行更改,就有可能改掉v1所引用的內容,甚至parser內部重新分配記憶體使v1成懸空指標。所以所有試圖對ParseIter實現的Iterator的嘗試肯定都是徒勞的。即便是用unsafe繞過編譯器檢查,也十有八九會導致UB,因為v1是不可變引用,而next內部呼叫parse函式使用的是mut引用,已經違反了rust的alias模型:要麼只有一個mut引用,要麼是多個不可變引用。

深究本質

上面是透過先假定,再匯出矛盾的反推方式,簡單直觀,方便理解,但不夠深入,其實出問題的本質原因是

透過

&‘b mut &’a mut T

,可以拿到

&‘b mut T

,但不能拿到

&’a mut T

透過

&‘b mut &’a T

,可以拿到

&‘b T

,而且也能拿到

&’a T

為方便分析,我把省略的生命週期標記出來。

impl<‘a> Iterator for ParseIter<’a> {

type Item = &‘a u8;

fn next<’b>(self: &‘b mut ParseIter<’a>) -> Option<&‘a u8> {

let buf: &’a [u8] = self。buf; // compile ok

let parser: &‘a mut Parser = self。parser; // compile error

return parser。parse(buf);

}

}

其中,self的型別是

&’b mut ParseIter<‘a>

,因此ParseIter裡的buf,可以類似為

&’b mut &‘a[u8]

, 而parser類似是

&’b mut &‘a mutParser

let buf:&’a [u8] = self。buf

,其實為透過

&‘b mut &’a T

拿到

&‘a T

,因此是編譯透過的;

let parser:&’a mut Parser = self。parser

,類似試圖透過

&‘b mut &’a mut T

&‘a mut T

,顯然就導致編譯報錯了。

憑啥mut就這麼特殊?

大家都知道rust對巢狀引用有自動解引用的功能,但是所解的引用的生命週期卻很少有人提。根據Deref和DerefMut的定義:

fn deref<’b>(&‘b self) -> &’b Self::Target;

fn deref_mut<‘b>(&’b mut self) -> &‘b mut Self::Target;

可知,解引用後得到的引用的生命週期是最外層引用的生命週期。

&’a mut T

實現了DerefMut有:

DerefMut::deref_mut<‘b>(self: &’b mut &’a mut T) -> &‘b mut T;

也就是透過

&’b mut &‘a mut T

,可以拿到

&’b mut T

&‘a T

實現了Deref有:

&’b mut &‘a T——>&’b &‘a T——>&’b T

那為啥透過

&‘b mut &’a T

,可以拿到

&‘a T

呢?這就得提到Clone和Copy這兩個基礎trait了。 他們的定義如下:

pub trait Clone: Sized {

fn clone<’b>(&‘b self) -> Self;

}

pub trait Copy : Clone {

}

根據alias規則,不可變引用可以存在多個例項,其本身也就一指標,size不大,所以標準庫對

&’a T

實現了Copy和Clone,也就是:

clone(&‘b &’a T)->&‘a T

,因此有:

&’b mut &‘a T——>&’b &‘a T——>&’a T

而mut引用必然是獨佔的,因此不可能實現Copy和Clone。

總結

rust裡的生命週期,所有權,alias模型和基礎trait等都是息息相關的,只有思考理解透徹才能融會貫通。