一個關於rust生命週期的問題分析
問題描述
前些天群裡有人遇到了個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等都是息息相關的,只有思考理解透徹才能融會貫通。