Rust中trait对象类型不匹配求助:返回boxed trait对象编译报错
我尝试从函数返回一个boxed trait对象的Result,看起来返回的类型满足该trait对象要求,但编译器提示类型不匹配。
我的代码如下:
use anyhow::Result; #[derive(Debug)] struct Bar { a: i32, b: i32, } trait Wrap {} struct Wrapper1<'a> { bar: &'a Bar, } impl<'a> Wrapper1<'a> { fn new(bar: &'a Bar) -> Result<Self> { Ok(Self { bar }) } } impl<'a> Wrap for Wrapper1<'a> {} struct Wrapper2<'a> { bar: &'a Bar, } impl<'a> Wrapper2<'a> { fn new(bar: &'a Bar) -> Result<Self> { Ok(Self { bar }) } } impl<'a> Wrap for Wrapper2<'a> {} fn wrap<'a>(i: i32, bar: &'a Bar) -> Result<Box<dyn Wrap + 'a>> { if i % 2 == 0 { Wrapper1::new(bar).map(Box::new) } else { Wrapper2::new(bar).map(Box::new) } }
编译器给出的错误信息:
error[E0308]: mismatched types --> src/main.rs:37:9 | 35 | fn wrap<'a>(i: i32, bar: &'a Bar) -> Result<Box<dyn Wrap + 'a>> { | -------------------------- expected `Result<Box<(dyn Wrap + 'a)>, anyhow::Error>` because of return type 36 | if i % 2 == 0 { 37 | Wrapper1::new(bar).map(Box::new) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Box<dyn Wrap>, Error>`, found `Result<Box<Wrapper1<'_>>, Error>` | = note: expected enum `Result<Box<(dyn Wrap + 'a)>, _>` found enum `Result<Box<Wrapper1<'_>>, _>` help: use `?` to coerce and return an appropriate `Err`, and wrap the resulting value in `Ok` so the expression remains of type `Result` | 37 | Ok(Wrapper1::new(bar).map(Box::new)?) | +++ ++ error[E0308]: mismatched types --> src/main.rs:39:9 | 35 | fn wrap<'a>(i: i32, bar: &'a Bar) -> Result<Box<dyn Wrap + 'a>> { | -------------------------- expected `Result<Box<(dyn Wrap + 'a)>, anyhow::Error>` because of return type ... 39 | Wrapper2::new(bar).map(Box::new) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Result<Box<dyn Wrap>, Error>`, found `Result<Box<Wrapper2<'_>>, Error>` | = note: expected enum `Result<Box<(dyn Wrap + 'a)>, _>` found enum `Result<Box<Wrapper2<'_>>, _>` help: use `?` to coerce and return an appropriate `Err`, and wrap the resulting value in `Ok` so the expression remains of type `Result` | 39 | Ok(Wrapper2::new(bar).map(Box::new)?) | +++ ++ For more information about this error, try `rustc --explain E0308`. error: could not compile `foo` due to 2 previous errors
按照编译器建议修改成Ok(Wrapper2::new(bar).map(Box::new)?)确实能编译,但我不理解为什么这一步是必要的——看起来类型并没有改变,而且这种写法也不够简洁。
核心原因:类型强制转换的时机问题
Rust中,将具体类型(比如Box<Wrapper1<'a>>)强制转换为trait对象(Box<dyn Wrap + 'a>)是一种隐式转换,但这种转换不会自动穿透Result容器。
在原始代码中,Wrapper1::new(bar).map(Box::new)返回的类型是Result<Box<Wrapper1<'a>>>,而函数要求返回Result<Box<dyn Wrap + 'a>>。虽然Box<Wrapper1<'a>>可以被强制转换为Box<dyn Wrap + 'a>,但Rust不会自动在Result内部完成这个转换——因为Result<T, E>的T是具体类型,编译器无法推断出需要把内部的T转换成trait对象。
编译器建议写法的原理
当你用?取出Result内部的值时:
Wrapper1::new(bar).map(Box::new)?会先处理错误:如果是Err就直接返回,如果是Ok就取出里面的Box<Wrapper1<'a>>。- 此时这个
Box<Wrapper1<'a>>会被隐式强制转换为Box<dyn Wrap + 'a>,因为它处于一个需要返回Result<Box<dyn Wrap + 'a>>的上下文里,外层的Ok()把这个转换后的trait对象重新包装成Result。
更简洁的写法
你可以不用?,而是手动显式转换类型,这样代码更简洁:
fn wrap<'a>(i: i32, bar: &'a Bar) -> Result<Box<dyn Wrap + 'a>> { if i % 2 == 0 { Wrapper1::new(bar).map(|w| Box::new(w) as Box<dyn Wrap + 'a>) } else { Wrapper2::new(bar).map(|w| Box::new(w) as Box<dyn Wrap + 'a>) } }
这里通过as关键字显式把Box<Wrapper1<'a>>转换成Box<dyn Wrap + 'a>,让编译器明确知道要做这个类型转换,不需要拆包再重新包装。
或者,也可以借助Into trait来简化:
fn wrap<'a>(i: i32, bar: &'a Bar) -> Result<Box<dyn Wrap + 'a>> { if i % 2 == 0 { Wrapper1::new(bar).map(Into::into) } else { Wrapper2::new(bar).map(Into::into) } }
因为Box<Wrapper1<'a>>可以自动实现Into<Box<dyn Wrap + 'a>>,所以用Into::into可以让编译器自动完成转换,代码更干净。
内容的提问来源于stack exchange,提问作者Andy Carlson

