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 {}。但錯誤型別還需要 DebugDisplay,這樣才能給出問題的資訊。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

這並不是很意外,因為我們知道特徵可以用在很多東西上,而且它們各自有不同的大小。