Rust 泛型及關(guān)聯(lián)類型

與其用它語言一樣Rust也支持泛型,這里記錄儀一下泛型的用法、特別是關(guān)聯(lián)類型associated type.
泛型系統(tǒng)
每種語言支持泛型的程度不一樣,根據(jù)<<An Extended Comparative Study of Language
Support for Generic Programming>>的描述,語言對泛型的支持基本可以用下面的表來評評估:

image.png

依據(jù)這個表,我們可以歸納一下Rust的泛型功能。
rust 的泛型類型參數(shù)必須寫在<>里,所以根據(jù)尖括號很容易區(qū)分類型參數(shù),并且類型參數(shù)的寫法遵循 [camel case]https://en.wikipedia.org/wiki/CamelCase)。

  1. multi-type concepts
    類型參數(shù)可以出現(xiàn)在struct, enum 以fn中。


    image.png

泛型也可以出現(xiàn)在method上.


image.png

Rust支持使用多個泛型參數(shù),代表兩個不同的類型:
make_tuplepair<T, U>(a: T, b: U) -> (T, U) {
(a, b)
}
struct paIr<T,U>{
x:T,
y:U
}
無論是單參數(shù),還是多參數(shù),都可以增加對參數(shù)的約束:
fn find_max<T : PartialOrd> (list : &[T]) -> T {
let mut max = &list[0];
for &i in list.iter() {
if i > max {
max = &i;
}
}
max
}
要使用比較操作'>',T必須實現(xiàn)PartialOrd(trait),否則系統(tǒng)在編譯的時候會報錯。
fn some_function<T:Display, U:clone>(t: T, u: U) -> i32
{
....
}

  1. Multiple constaints
    Rust允許泛型參數(shù)具有多個約束,以上面過find_max為例,我們將對list[0]引用改為move(去掉&),這時候我們需要增加額外的約束。


    image.png

    當泛型參數(shù)有多個約束時,通常使用where語句的方式表示:


    image.png
  2. Associated type access
    rust的關(guān)聯(lián)類型與trait的泛型有關(guān),允許trait 內(nèi)部定義新類型。
    關(guān)聯(lián)類型使用Type在trait內(nèi)部定義一個占位符,具體實現(xiàn)時聲明占位符的類型。我們最常見的寫法如下:


    image.png

    type 定義的Item只是一個類型占位符,在具體實現(xiàn)時聲明Item的具體類型。
    另外,與泛型相比較,可以提高代碼的可讀性,如下實現(xiàn)一個contain的類型, 需要在實現(xiàn)方法上注明具體類型,同時比較復雜的包含關(guān)系是,需要聲明所有的泛型參數(shù):


    image.png

    但如果改用關(guān)聯(lián)類型,可讀性就會大大提高:
    image.png

    但是關(guān)聯(lián)類型與泛型還是存在重要的不同,來源于其聲明方式:
    //關(guān)聯(lián)類型

    impl Contains for Container {
    }
    //泛型
    impl<i32,i32> Contains for Container {
    }
    關(guān)聯(lián)類型的方式只能聲明一次,而泛型可以聲明多次,泛型可以對不同的具體類型進行聲明,以上名的關(guān)聯(lián)類型具體來看看泛型的聲明:


    image.png
  3. Constraints on associated type
    這個Rust 中的關(guān)聯(lián)類型也可以加限制,通過聲明需要實現(xiàn)的trait來限制 , 參考 關(guān)聯(lián)類型定義。
    An associated type declaration declares a signature for associated type definitions. It is written as type, then an identifier, and finally an optional list of trait bounds.

    image.png

    如上圖, 在關(guān)聯(lián)類型的具體實現(xiàn)時,需要type的具體類型滿足同時實現(xiàn)Debug的限制。

  4. Retroactive modeling

  1. type ailase
    rust 通過Type 關(guān)鍵字來聲明一個類型的別名:
    語法:type Name = ExistingType;

type Meters = u32;
type Kilograms = u32;

let m: Meters = 3;
let k: Kilograms = 3;

assert_eq!(m, k);
類型別名的寫法,類似于關(guān)聯(lián)類型,實際上也確實有關(guān)聯(lián)算是特殊一種類型別名(Associated types are type aliases associated with another type
)

  1. Separate compilation

  2. Implicit argument deduction

除了通過上面的不同標準來熟悉泛型在Rust中的應用,下面還還總結(jié)了一些沒有在標準的功能,以及一些很特別的用法。
a. 泛型默認類型


image.png

這里Add<RHS = Self>是默認泛型類型參數(shù),表示如果不顯示指定泛型類型,就默認泛型類型為Self。
當使用泛型類型參數(shù)時,可以為泛型指定一個默認的具體類型。如果默認類型就足夠的話,這消除了為具體類型實現(xiàn) trait 的需要。為泛型類型指定默認類型的語法是在聲明泛型類型時使用 <PlaceholderType=ConcreteType>。

b. rust 文檔中提到的一種container的寫法
trait Container {
type E;
fn empty() -> Self;
fn insert(&mut self, elem: Self::E);
}
impl<T> Container for Vec<T> {
type E = T;
fn empty() -> Vec<T> { Vec::new() }
fn insert2(&mut self, x: T) { self.push(x); }
}


image.png

上面這個容器的寫法, Type 定義的關(guān)聯(lián)類型在impl的時候仍是不知道的, 這樣感覺擴展了關(guān)聯(lián)類型的使用范圍。

c. 幻影類型 Phantom Type
幻影類型,是不會存儲任何數(shù)據(jù),利用泛型約束的特點,用于編譯期的檢查等,不會影響運行期行為。 幻影類型和std::marker::PhantomData幻影數(shù)據(jù)一起使用,PhantomData作為標志符表示不存在的數(shù)據(jù),只用來占位消費幻影類型。
// A phantom tuple struct which is generic over A with hidden parameter B.
struct PhantomTuple<A, B>(A,PhantomData<B>);

// A phantom type struct which is generic over A with hidden parameter B.
struct PhantomStruct<A, B> { first: A, phantom: PhantomData<B> }
如上,B是幻影類型,和PhantomData一切占位,實際不存儲任何數(shù)據(jù)。

網(wǎng)上有一篇博客專門講 Phantom Type, 提到了下面幾種用法:

  • Compile time type check
    如下圖,


    image.png
  • Unused lifetime parameters
    在寫一些unsafe的代碼(FFI,數(shù)據(jù)結(jié)構(gòu)),我們常會遇到在定義struct的時候type或者lifetime和struct 是邏輯是關(guān)聯(lián)的,但卻不是struct字段的一部分。列入如下代碼, 只包含unsafe 指針,由于語法規(guī)范沒有a,所以直接在struct字段上聲明‘a(chǎn)會報錯:


    image.png

    但是我們確實希望iter具有'a的聲明周期,如同聲明。這時候我們可以通關(guān)添加幻影類型,來實現(xiàn)。


    image.png

    這樣,Iter的lifetime是 ‘a(chǎn) 也就是不能超過 ‘a(chǎn) 的生命周期.
  • Unused type parameters
    這里和上邊的情況類似這次是FFI場景下,Rust也是禁止在struct中定義未使用的類型參數(shù),這種情況用PhantomData進行包裹.下圖也是網(wǎng)上其它博客給出的例子。


    image.png

    如上圖代碼, resource_type 實際上是一個擁有未定義類型R的站位屬性字段。

  • Ownership and the drop check
    雖然Rust中有各種規(guī)則保證聲明周期管理,但事后有時候也不好用:
    下面這個代碼片段服務編譯通過,因為編譯器服務確定inspector, days誰的聲明周期更長,如果day的生命周期短,inspector = Inspector(&days);就會造成空指針。
    struct Inspector<'a>(&'a u8);

impl<'a> Drop for Inspector<'a> {
fn drop(&mut self) {
// 1. 因為無法確認days和inspector的生命周期到底誰長,如果days先被drop這里就會構(gòu)成閑空指針
println!("I was only {} days from retirement!", self.0);
// 2. 現(xiàn)在的編譯器還沒有智能到能看出drop的方法實現(xiàn)即使不使用也不能通過編譯
//println!("Just drop!");
}
}

fn main() {
let (inspector, days);
days = Box::new(1);
inspector = Inspector(&days);
}

rust里,struct內(nèi)有生命周期參數(shù)的,要求使用生命周期的參數(shù)生命周期長于struct, 這樣才不會造成空指針。
在上例中,Inspector生命有自己的drop方法和一個引用參數(shù)(&),而在main函數(shù)實例化時,我們看到這個引用參數(shù)是days。
上面的這份代碼,編譯的時候會報錯,提示不能確定days的生命周期是否足夠長,原因是這樣的:

  1. let (inspector, days); inspector 在day的前面聲明,根據(jù)rust的規(guī)則,一個scope的變量已聲明的反序drop掉(當超出scope時)。所以,days會在inspector前面被drop掉。
  2. inspector = Inspector(&days);days作為引用參數(shù)傳給了inspector, 這是后rust需要, days的聲明周期長于inspector. 總結(jié)為:對于一個實現(xiàn)了Drop的范型類型對于它的范型參數(shù)必須嚴格的超過它
  3. 通過#[may_dangle]明確指明不會去使用借用的數(shù)據(jù)

3。inspector 聲明了自己的drop trait的實現(xiàn),drop函數(shù)會在inspector被drop的時候調(diào)用。 但是問題來了, 編譯器無法判斷是否drop函數(shù)中有引用days。
所以當執(zhí)行完inspector = Inspector(&days); 到達scope的終點時:rust開始先drop days;rust 在開始調(diào)用inspector的drop方法,開始 drop inspector。在這個方法里,如果引用了days的值 println!("I was only {} days from retirement!", self.0); 和顯然就產(chǎn)生了空指針。
為了避免這個錯誤,rust的編譯器在發(fā)現(xiàn)某個struct有自己實現(xiàn)drop, 并引用了統(tǒng)一作用范圍的參數(shù),該參數(shù)優(yōu)先收回時,會自動報編譯錯誤。
直接改上面這段錯誤的方式:
方法一. 移除自己實現(xiàn)的drop方法。
方法二. 修改變量定義順序, let (days,inspector ); 滿足:對于一個實現(xiàn)了Drop的范型類型對于它的范型參數(shù)必須嚴格的超過它

對于下面一個Vec類型:
struct Vec<T> {
data: *const T, // *const for variance!
len: usize,
cap: usize,
}
雖然沒有’a 聲明周期參數(shù),檢查也不會報錯,但是drop檢查器會認為Vec 并不擁有T, 兩者之間不存在約束(聲明周期約束),這就會導致可能在drop函數(shù)中訪問到已經(jīng)被析構(gòu)的數(shù)據(jù)導致懸空指針(例如自己實現(xiàn)drop并訪問T). 這個問題可以通過PhantomData來解決這個問題:


image.png

另一重方式,是參考標準庫中的Unique<T>工具類,它默認實現(xiàn)了上面的功能。
額外的參考:


image.png
  1. dropck
  2. phantom-data
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容