用Box包裹trait

Box对于返回trait非常有用。你可以像这个例子一样用泛型函数写trait:

use std::fmt::Display;

struct DoesntImplementDisplay {}

fn displays_it<T: Display>(input: T) {
    println!("{}", input);
}

fn main() {}

这个只能接受Display的东西,所以它不能接受我们的DoesntImplementDisplay结构。但是它可以接受很多其他的东西,比如String

你也看到了,我们可以使用 impl Trait 来返回其他的trait,或者闭包。Box也可以用类似的方式使用。你可以使用 Box,否则编译器将不知道值的大小。这个例子表明,trait可以用在任何大小的东西上:

#![allow(dead_code)] // Tell the compiler to be quiet
use std::mem::size_of; // This gives the size of a type

trait JustATrait {} // We will implement this on everything

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], // This one will be very large
    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>(),
    );
}

当我们打印这些东西的size的时候,我们得到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

而这是真的,因为size可以是2,3,32,1002,或者其他任何东西。所以我们把它放在一个Box中。在这里我们还要加上dyn这个关键词。dyn这个词告诉你,你说的是一个trait,而不是一个结构体或其他任何东西。

所以你可以把函数改成这样。


#![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 {} // Now it is an error type with Debug. Time for Display:

impl fmt::Display for ErrorOne {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "You got the first error!") // All it does is write this message
    }
}


#[derive(Debug)] // Do the same thing with 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!")
    }
}

// Make a function that just returns a String or an error
fn returns_errors(input: u8) -> Result<String, Box<dyn Error>> { // With Box<dyn Error> you can return anything that has the Error trait

    match input {
        0 => Err(Box::new(ErrorOne)), // Don't forget to put it in a box
        1 => Err(Box::new(ErrorTwo)),
        _ => Ok("Looks fine to me".to_string()), // This is the success type
    }

}

fn main() {

    let vec_of_u8s = vec![0_u8, 1, 80]; // Three numbers to try out

    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

这并不奇怪,因为我们知道,一个trait可以作用于很多东西,而且它们各自有不同的大小。