Box 包裹的特徵
Box
對於回傳特徵非常有用。你知道你可以把特徵用在泛型函式就像這個範例:
use std::fmt::Display; struct DoesntImplementDisplay {} fn displays_it<T: Display>(input: T) { println!("{}", input); } fn main() {}
這個函式只能接受是 Display
的東西,所以它不能接納我們的 DoesntImplementDisplay
結構體。但是它可以接受很多其他的東西,比如 String
。
你也看到了,我們可以使用 impl 特徵
來回傳其他的特徵或閉包。Box
也可以用類似的方式來使用。你可以使用 Box
是因為不這樣編譯器將不會知道值的大小。這個範例證明特徵可以用在任何大小的東西上:
#![allow(dead_code)] // 告訴編譯器要安靜 use std::mem::size_of; // 這會給出型別的大小 trait JustATrait {} // 我們將會實作這個在所有東西上 enum EnumOfNumbers { I8(i8), AnotherI8(i8), OneMoreI8(i8), } impl JustATrait for EnumOfNumbers {} struct StructOfNumbers { an_i8: i8, another_i8: i8, one_more_i8: i8, } impl JustATrait for StructOfNumbers {} enum EnumOfOtherTypes { I8(i8), AnotherI8(i8), Collection(Vec<String>), } impl JustATrait for EnumOfOtherTypes {} struct StructOfOtherTypes { an_i8: i8, another_i8: i8, a_collection: Vec<String>, } impl JustATrait for StructOfOtherTypes {} struct ArrayAndI8 { array: [i8; 1000], // 這一個將會非常大 an_i8: i8, in_u8: u8, } impl JustATrait for ArrayAndI8 {} fn main() { println!( "{}, {}, {}, {}, {}", size_of::<EnumOfNumbers>(), size_of::<StructOfNumbers>(), size_of::<EnumOfOtherTypes>(), size_of::<StructOfOtherTypes>(), size_of::<ArrayAndI8>(), ); }
當我們列印這些東西大小的時候,我們得到 2, 3, 32, 32, 1002
。所以如果你像下面這樣做的話會造成錯誤:
#![allow(unused)] fn main() { // ⚠️ fn returns_just_a_trait() -> JustATrait { let some_enum = EnumOfNumbers::I8(8); some_enum } }
它說:
error[E0746]: return type cannot have an unboxed trait object
--> src\main.rs:53:30
|
53 | fn returns_just_a_trait() -> JustATrait {
| ^^^^^^^^^^ doesn't have a size known at compile-time
而這是真的,因為大小可以是 2、3、32、1002,或者其他任何東西。所以我們把它放在 Box
中。在這裡我們還加上了 dyn
這個關鍵詞。dyn
這個詞告訴你,你說的是個特徵,而不是結構體或其他任何東西。
所以你可以把函式改成這樣:
#![allow(unused)] fn main() { // 🚧 fn returns_just_a_trait() -> Box<dyn JustATrait> { let some_enum = EnumOfNumbers::I8(8); Box::new(some_enum) } }
現在它能執行了,因為在堆疊上只是個 Box
,而我們也知道 Box
的大小。
你會經常看到 Box<dyn Error>
這種形式,因為有時你可能會有多個可能的錯誤。
我們可以快速建立兩個錯誤型別來顯示這一點。要建立正式的錯誤型別,你必須為它實作 std::error::Error
。這部分很容易:只要寫出 impl std::error::Error {}
。但錯誤型別還需要 Debug
和 Display
,這樣才能給出問題的資訊。Debug
很容易,只要加上 #[derive(Debug)]
就行,但 Display
需要 .fmt()
方法。我們之前做過一次。
程式碼像這樣:
use std::error::Error; use std::fmt; #[derive(Debug)] struct ErrorOne; impl Error for ErrorOne {} // 現在錯誤型別有 Debug 了. 換 Display: impl fmt::Display for ErrorOne { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "You got the first error!") // 所有要做的就是寫這段訊息 } } #[derive(Debug)] // 對 ErrorTwo 做一樣的事 struct ErrorTwo; impl Error for ErrorTwo {} impl fmt::Display for ErrorTwo { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "You got the second error!") } } // 做出只回傳 String 或錯誤的函式 fn returns_errors(input: u8) -> Result<String, Box<dyn Error>> { // 有了 Box<dyn Error> 你就能回傳任何有 Error 特徵的東西 match input { 0 => Err(Box::new(ErrorOne)), // 不要忘記放進 Box 裡 1 => Err(Box::new(ErrorTwo)), _ => Ok("Looks fine to me".to_string()), // 這是成功的型別 } } fn main() { let vec_of_u8s = vec![0_u8, 1, 80]; // 用來嘗試的三個數字 for number in vec_of_u8s { match returns_errors(number) { Ok(input) => println!("{}", input), Err(message) => println!("{}", message), } } }
將會印出:
You got the first error!
You got the second error!
Looks fine to me
如果我們在沒有 Box<dyn Error>
時寫成這樣,我們就會有問題了:
#![allow(unused)] fn main() { // ⚠️ fn returns_errors(input: u8) -> Result<String, Error> { match input { 0 => Err(ErrorOne), 1 => Err(ErrorTwo), _ => Ok("Looks fine to me".to_string()), } } }
它會告訴你:
21 | fn returns_errors(input: u8) -> Result<String, Error> {
| ^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
這並不是很意外,因為我們知道特徵可以用在很多東西上,而且它們各自有不同的大小。