用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 {}
。但错误还需要Debug
和Display
,这样才能给出问题的信息。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可以作用于很多东西,而且它们各自有不同的大小。