正如您可能已經知道的,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)
}
}
感謝這樣一個完整的答案。我不知道所有的「默認是大小但自己不是」部分。這是我困惑的主要原因。 – eulerdisk