2015-06-19 39 views
9

我有兩個相關功能的一個特點:爲什麼在這個特徵中需要「Sized」邊界?

trait WithConstructor: Sized { 
    fn new_with_param(param: usize) -> Self; 

    fn new() -> Self { 
     Self::new_with_param(0) 
    } 
} 

爲什麼第二種方法(new())的默認實現逼我把綁定在類型Sized?我認爲這是因爲堆棧指針操作,但我不確定。

如果編譯器需要知道大小的棧上分配內存, 爲什麼下面的例子中不需要SizedT

struct SimpleStruct<T> { 
    field: T, 
} 

fn main() { 
    let s = SimpleStruct { field: 0u32 }; 
} 

回答

21

正如您可能已經知道的,Rust中的類型可以調整大小和未定義。顧名思義,Unsized類型不具有存儲編譯器已知的此類型值所需的大小。例如,[u32]是未分類的u32 s的數組;因爲元素的數量沒有在任何地方指定,編譯器不知道它的大小。另一個例子是一個裸性狀對象類型,例如,Display,當它被直接用作類型:

let x: Display = ...; 

在這種情況下,編譯器不知道哪種類型實際上這裏使用時,它被擦除時,因此它不知道這些類型值的大小。上面的行是無效 - 你不能使一個局部變量在不知道其大小(分配堆棧上的足夠的字節),並且不能將未施膠類型的值傳遞到一個函數作爲參數或從一個返回。可以通過指針使用未分類的類型,但可以攜帶附加信息 - 分片的可用數據長度(&[u32])或指向虛擬表的指針(Box<SomeTrait>)。由於指針總是具有固定且已知的大小,因此它們可以存儲在局部變量中並傳遞給函數或從函數返回。

考慮任何具體類型,你總是可以說無論是規模還是未施膠。然而,對於泛型,會出現一個問題 - 是否有一些類型參數是大小的?

fn generic_fn<T>(x: T) -> T { ... } 

如果T是未分級的,那麼這樣的函數定義不正確,因爲你不能左右直接傳遞未分級值。如果大小合適,那麼一切正常。

生鏽所有泛型類型參數的默認大小隨處可見 - 在功能,結構和特徵。它們有一個隱含的Sized界限; Sized是用於標記尺寸類型的一個特點:

fn generic_fn<T: Sized>(x: T) -> T { ... } 

這是因爲,在鋪天蓋地的次數你想你的泛型參數的大小。然而,有時你會想退出sizedness的,這可以用?Sized約束來實現:

fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... } 

現在generic_fn可以這樣調用generic_fn("abcde"),並T將與str這是無膠被實例化,但沒關係 - 該函數接受對T的引用,所以沒有什麼不好的事情發生。

但是,還有另一個地方,尺寸的問題是重要的。 Rust中的特徵總是適用於某些類型:

trait A { 
    fn do_something(&self); 
} 

struct X; 
impl A for X { 
    fn do_something(&self) {} 
} 

但是,這只是爲了方便和實用性的目的。它可以定義特質總是採取一種類型的參數並沒有指定性狀爲實現類型:

// this is not actual Rust but some Rust-like language 

trait A<T> { 
    fn do_something(t: &T); 
} 

struct X; 
impl A<X> { 
    fn do_something(t: &X) {} 
} 

這就是Haskell的類型類是如何工作的,而且,事實上,這是怎樣的特徵實際上是在實施生鏽在較低的水平。

Rust中的每個特徵都有一個隱式類型參數,名爲Self,它指定了該特徵實現的類型。它始終可用於性狀的身體:

trait A { 
    fn do_something(t: &Self); 
} 

這就是尺寸問題進入圖片的地方。 Self參數的大小是?

事實證明,沒有,Self在默認情況下不在Rust中調整大小。每個特徵都有一個隱含的?Sized,在Self上。其中一個原因是需要的,因爲有很多特性可以用於未經處理的類型並且仍然有效。例如,只有包含僅通過引用才能獲取並返回Self的方法的任何特徵都可以針對未分類的類型實現。您可以在RFC 546中閱讀更多關於動機的內容。

當您僅定義特徵及其方法的簽名時,尺寸不是問題。由於這些定義中沒有實際的代碼,因此編譯器不能採取任何措施。但是,當您開始編寫使用此特徵的通用代碼時,應考慮尺寸,其中包含默認方法,因爲它們採用隱含的Self參數。因爲Self的默認大小不是默認的,所以默認特徵方法不能按值返回Self,或按值作爲參數。因此,您可能需要指定Self必須默認大小:

trait A: Sized { ... } 

,或者你可以指定一個方法只能被稱爲如果Self的大小:

trait WithConstructor { 
    fn new_with_param(param: usize) -> Self; 

    fn new() -> Self 
    where 
     Self: Sized, 
    { 
     Self::new_with_param(0) 
    } 
} 
+0

感謝這樣一個完整的答案。我不知道所有的「默認是大小但自己不是」部分。這是我困惑的主要原因。 – eulerdisk

4

讓我們來看看如果你用未定型的類型做了這個,會發生什麼。

new()移動到您的new_with_param(_)方法的結果給調用者。但除非類型大小,應該移動多少個字節?我們根本無法知道。這就是爲什麼移動語義需要Sized類型。

注:各種Box ES已經被設計爲準確這一問題提供運行時服務。

+2

爲什麼它不抱怨關於'new_with_param'雖然?它還要求在其調用者的堆棧上保留適量的空間。 –

+0

所以我的想法是正確的,但是爲什麼'Size'在通用結構中不是必需的?我更新了這個問題。 – eulerdisk

+0

@Matthieu M.'new_with_param'只是一個特徵方法的定義,而不是一個實現。 – llogiq