Stories

Detail Return Return

rust學習二十.12、RUST動態大小類型DST以及Sized特質 - Stories Detail

DST(dynamic size type)-中譯“動態大小類型"。本文簡要討論動態大小類型的一些問題。

一、前言

rust作為一門靜態類型語言,和大部分其它靜態類型語言(C,C++,C#,JAVA)一樣,希望在編譯的時候知道每個實例/類型的大小。

作為靜態類型語言,優點是毋庸置疑的的:

1.類型錯誤(如字符串與整數運算)在編譯階段即可被捕獲,減少運行時崩潰風險

2.編譯器可基於類型信息優化內存分配與代碼執行效率

但無論哪一種靜態類型語言,都有同樣的問題:實際業務場景中,必然有動態大小的類型,那麼應該如何處理了?

每個靜態類型語言都有它的處理機制,但由於rust的設計哲學和目標,所以它的處理方式是非常特別的!

無論如何,rust必須能夠處理動態大小類型,否則這個語言無法用(或者變為極其難用的玩具)。

二、RUST動態類型

如前,rust也有動態類型,例如常見的str。

只是可惜的是,我們常用的其實是&str,注意不是str。如果直接let a:str="abc"是報告編譯錯誤的:

doesn't have a size known at compile-time
現在聊聊動態類型的幾個問題.

2.1、如何處理動態大小類型

rust使用了新類型設計模式(個人更願意看作是封裝模式)來解決這個問題。

具體而言就是用智能指針來解決這個問題。

如我們所知,智能指針的典型結構:一個指向數據的指針、少量的其它元數據、額外實現的一些特質(例如Deref,Drop).

因此,編譯器會把智能指針視為一個固定大小。 是的,String也可以看作式某種智能指針。

我們來看經典的Box盒子指針的定義:

pub struct Box<
    T: ?Sized,
    #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global,
>(Unique<T>, A);
#[lang = "ptr_unique"]
pub struct Unique<T: ?Sized> {
    pointer: NonNull<T>,
    // NOTE: this marker has no consequences for variance, but is necessary
    // for dropck to understand that we logically own a `T`.
    //
    // For details, see:
    // https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data
    _marker: PhantomData<T>,
}
pub struct NonNull<T: ?Sized> {
    pointer: *const T,
}
pub struct PhantomData<T: ?Sized>;
#[unstable(feature = "allocator_api", issue = "32838")]
pub unsafe trait Allocator {
    //此處略
}

注意,T是?Sized,意思T可以是固定大小或者動態大小。其次根據文檔説明 ?T(T是特質)目前只能有?Sized,換言之,不會有?Drop,?Deref之類的申明。

在Box的底層通過 *const T(不可變原始指針)來指向實際的數據,整體上可以看作是固定大小的。

 

2.2、Sized特質

Sized特質,故名思意就是大小的意思,在rust中表示一個特質,表示被它綁定(限定)的類型是固定大小。rust編譯器會為每一個添加了Sized特質的類型實現具體內容。

?Size則表示類型是可以固定也可以不是固定大小。其次根據文檔説明 ?T(T是特質)目前只能有?Sized,換言之,不會有?Drop,?Deref之類的申明。

看看Box的定義就是知道類型是?Sized,而實踐也告訴我們,可以在Box中存放標量類型和堆棧類型。

2.3、動態分發(dyn)

動態分發(dynamic dispatch),意思就是在運行的時候才確定特質對應的實際類型。

在編碼的時候,如果使用指針存儲一個特質,那麼必須添加一個dyn,這樣rustc通過編譯,並在運行的時候,會把特質替換為實際的類型實例。

三、示例

trait Animal {
    fn eat(&self);
}

struct Tiger {
    name: String,
    age: u8,
}
struct Pig {
    name: String,
    age: u8,
}

impl Animal for Tiger {
    fn eat(&self) {
        println!("{}歲{} 正在吃野豬", self.age, self.name);
    }
}

impl Animal for Pig {
    fn eat(&self) {
        println!("{}歲{} 正在吃竹筍和地瓜", self.age, self.name);
    }
}

impl Tiger {
    fn clone(&self) -> Tiger {
        Tiger {
            name: self.name.clone(),
            age: self.age,
        }
    }
}

fn feed_animal_dyn(animals: Vec<Box<dyn Animal>>) {
    for animal in animals {
        animal.eat();
    }
}
/**
 * Sized特質測試,告知編譯器這個參數可以是固定大小也可以是動態大小,而非固定大小
 */
fn feed_animal<T: Sized>(animal: T)
where
    T: Animal,
{
    animal.eat();
}

fn main() {

    //這樣定義會報告編譯錯誤:doesn't have a size known at compile-time
    //let me:str="lzf"; 
    dst_test();
    dyn_test();
    sized_test();
}

fn sized_test() {
    let tiger = Tiger {
        name: "大王🐯".to_string(),
        age: 3,
    };
    let 武松的老虎 = tiger.clone();
    feed_animal(武松的老虎);
}

/**
 * 動態大小類型測試,使用&T和Box<T>來封裝動態大小類型(DST)
 */
fn dst_test() {
    //Box指針封裝動態大小類型(DST).編譯器認為Box指針式固定大小,典型的障眼法
    let code: Box<str> = Box::from("Hello, world!");
    println!("{}", code);
    let name:Box<&str> = Box::from("狄仁傑");
    println!("{}", name);

}
/**
 * 動態分發測試,使用dyn告訴編譯器這是一個動態分發,而非靜態分發,是特質而不是其它的類型(stuct,enum等)
 */
fn dyn_test() {
    let tiger = Tiger {
        name: "鬆崽🐅".to_string(),
        age: 10,
    };

    let pig = Pig {
        name: "小胖🐖".to_string(),
        age: 5,
    };
    // 必須使用as 關鍵字,將Tiger和Pig轉換為特質對象(trait object)
    // 必須使用dyn關鍵字,告訴編譯器這是一個動態分發(dynamic dispatch),即在運行的時候才使用具體的類型
    let animals = vec![
        Box::new(tiger) as Box<dyn Animal>,
        Box::new(pig) as Box<dyn Animal>,
    ];
    feed_animal_dyn(animals);
}

本例基本模仿了書本上的例子,演示了三種情況:

1.不能直接定義一個動態大小類型,否則編譯器報錯

2.如何用智能指針處理動態大小類型,以便可以通過編譯

3.Sized特質的使用,以及如何使用Box存儲特質。使用了dyn(動態分發)

動態分發(dyn)

函數fn feed_animal_dyn(animals: Vec<Box<dyn Animal>>)只接收Animal特質的Box指針向量,無法定義為:

fn feed_animal_dyn(animals: Vec<Box<Animal>>),這是因為特質本身是無意義的,必須和特定類型關聯,要關聯必然涉及到動態分發。

 

演示結果:

 

四、小結

1.絕大部分靜態類型語言都支持固定大小類型和動態大小類型,包括rust

2.rust使用指針來解決動態大小類型的編譯問題和運行問題

3.動態分發機制可以解決指針中定義特質的問題

Add a new Comments

Some HTML is okay.