[英]Does lifetime elision work for methods in trait impls?
有了這個問題,我正在尋找那些在這方面有更多知識的人的反饋。 我絕不是專家。 所以我不妨提前問我的問題:我的推理是否正確?
根據這里關於SO 的問題的答案 ,我很困惑地看到在實施特征方法時終生沒有了:
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<T>) -> bool {
self.0 as *const T == other.0 as *const T
}
}
這里,在方法簽名中,在other
類型上省略了生命周期'b
。 這是有效的,也是正確的。 我期望它是&RefEquality<'b, T>
,因為類型是正確的。 畢竟,這里的'b
是必不可少的:生命必須與'a
不同。 如果沒有,那就太限制了:實現只適用於另一個與Self
相同的RefEquality<T>
。 所以這些顯然是不同的語義。 編譯器如何推斷出正確的生命周期?
可以省略函數簽名的生命周期,但不能在impl塊上省略它們。 在那里,必須完全指定類型,包括命名生命周期。
另一方面,在eq()
方法上,我能夠在其他類型注釋中忽略生命周期。 實際上,編譯器然后為它插入任意生命周期,這明顯不同於'a
。 這就是為什么這樣做同時保持相同語義的原因:
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq<'c>(&self, other: &RefEquality<'c, T>) -> bool {
self.0 as *const T == other.0 as *const T
}
}
在這里,我為該方法引入了一個任意的生命周期'c
,這與生命周期省略時的編譯器基本相同。
在我的特質impl中命名一輩子'b
只是聲明它必須與'a
(我也沒有以任何方式鏈接它們)不同。 從邏輯上講,這不起作用:
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'a, T>) -> bool {
self.0 as *const T == other.0 as *const T
}
}
我在impl中說過類型會有所不同(基於它們的生命周期),但現在實際的eq()
實現說它們是相同的。 這會導致預期的類型錯誤。
如果我希望生命時間平等怎么辦? 在這種情況下,我仍然可以使用生命周期省略,還是編譯器會插入任意生命周期並報告類型錯誤? 事實證明,推理在這里也能正常工作:
impl<'a, T> PartialEq<RefEquality<'a, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<T>) -> bool {
self.0 as *const T == other.0 as *const T
}
}
省略的生命周期將被推斷為'a
,保持RefEquality<T>
類型必須具有相同生命期望的期望語義。
讓我們看看rustc的過程,以確定提供的impl
方法是否對應於特征中聲明的簽名。
在代碼中的位置compare_impl_method
在librustc_typeck/check/compare_method.rs
,它的很好的注釋,但連評論都難以用於那些沒有編譯器的黑客。
我不是編譯器開發人員,所以以下是基於我的生銹經驗和解釋!
特征中的聲明對應於特定的函數類型,並且impl
塊中的定義被解析為其自己的函數類型。
對於這個問題,我認為只有類型檢查的結論才是重要的:
impl
函數是特質函數的子類型嗎?” 如果S是T的子類型,則子類型關系通常寫為S <:T,意味着可以在期望類型為T的項的上下文中安全地使用類型S的任何項。
這聽起來很合理。 我們希望impl
塊定義一個可以安全使用的函數,就好像它是在trait中聲明的函數一樣。
這是完整的終生案例,但明確說明。 我已經用恐慌替換了所有方法體,以強調函數簽名檢查完全不受函數體的影響。
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq<'c>(&self, other: &RefEquality<'c, T>) -> bool {
panic!()
}
}
該特征需要一個類型的函數:
fn(&RefEquality<'a, T>, &RefEquality<'b, T>)
您提供類型的功能:
fn<'c>(&RefEquality<'a, T>, &RefEquality<'c, T>)
看起來所提供的impl比所需的“更通用”。 使用'c == 'b
,則函數的類型相同。
它是預期類型的子類型,因為我們總是可以安全地使用fn<'c>
版本。
對於你的第二個例子,那個沒有編譯:
impl<'a, 'b, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'a, T>) -> bool {
panic!()
}
}
你可以添加一個綁定的'b: 'a
('b outlives'a),然后就可以了 :
impl<'a, 'b: 'a, T> PartialEq<RefEquality<'b, T>> for RefEquality<'a, T> {
fn eq(&self, other: &RefEquality<'a, T>) -> bool {
panic!()
}
}
該特征需要一個類型的函數:
fn(&RefEquality<'a, T>, &RefEquality<'b, T>)
您提供類型的功能:
fn(&RefEquality<'a, T>, &RefEquality<'a, T>)
我認為,如果'b outlives'a,它們是兼容的似乎是合乎邏輯的,但讓我們冷靜地看待它。
讓我們刪除常數因素:
該特征需要一個類型的函數:
fn(Ref<'b>)
您提供類型的功能:
fn(Ref<'a>)
我們還有where 'b: 'a
。 我們怎么能看到它們兼容?
子類型:使用X
代替Y
是否安全?
方差: 如果 X
是Y
的子類型,那么Foo<X>
和Foo<Y>
呢?
另見Wikipedia , Rustonomicon on variance。
生命周期的子類型定義是:
'x <: 'y
表示'x
長於'y
。
讓我們練習使用引用的子類型和方差。
什么時候使用&'x i32
而不是&'y i32
是否&'y i32
?
當'x
比'y
更長壽時,那么更換是安全的。 'x
生命長於'y
意味着&'x i32
是&'y i32
的子類型:
'x <: 'y => &'x i32 <: &'y i32
子類型關系在同一方向傳播,這稱為協方差 ; &'a i32
在'a
參數中是協變的。
相反,函數的方差行為是這樣的:
X <: Y => fn(Y) <: fn(X)
函數的行為方式與其參數類型相反。 這是逆轉 ,邏輯上“反對”,因為它是相反的方向。
對於這個問題,我們假設Ref<'a>
行為就好像它包含一個&'a
引用,並且它&'a
本身具有相同的方差 。
我們被賦予了where 'b: 'a
的界限,這意味着:
'b <: 'a
使用協方差規則進行參考和參考:
'b <: 'a => Ref<'b> <: Ref<'a>
對函數使用逆變規則**
Ref<'b> <: Ref<'a> => fn(Ref<'a>) <: fn(Ref<'b>)
這就是rustc問的問題, impl
函數是特質函數的一個子類型。 它是!
** wrt函數參數 :
如果你的目標只是為相同的終生案例定義PartialEq
,那么是的, PartialEq
lifetime case就可以了。 它在impl中提供了更通用的功能,但類型檢查器確定它是兼容的。
您還可以根據生命周期參數更改RefEquality
類型的方差。
如果你想要一個RefEquality<'a, T>
只是與完全相同的生命周期兼容的子類型,那就叫做不變性 。
你可以使用一個具有不變性的原語, std::cell::Cell<T>
。 Cell<T>
在T
參數中是不變的。
完成此操作的常用方法是PhantomData
成員:
struct RefEquality<'a, T: 'a> {
ptr: &'a T,
marker: PhantomData<Cell<&'a ()>>,
}
如果你想看到一個不變量的應用,請查看crossbeam crate以及在'a
參數中不變量的Scope<'a>
是其安全范圍線程的特殊借用規則的基石。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.