Rust no_std环境下错误处理代码差异及报错原因咨询
背景
原本在std环境中可正常运行的代码:
fn read_inner<'b>(&'b mut self, buf: &mut [u8]) -> Result<usize, Box<dyn Error + 'b>> { ... buf[rval] = reader.next_spi_byte()? ...
为迁移到no_std环境,改用core_error crate后出现如下编译报错:
error[E0277]: `?` couldn't convert the error to `Box<dyn core_error::Error>` --> /home/thoth/src/rust-esp32-experiments/http-camera/webcam-applib/src/camera.rs:90:47 | 90 | buf[rval] = reader.next_spi_byte()?; | ^ the trait `From<CamError<<CS as embedded_hal::digital::v2::OutputPin>::Error, ES, EI>>` is not implemented for `Box<dyn core_error::Error>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `From<T>`: <Box<CStr> as From<&CStr>> <Box<CStr> as From<CString>> <Box<CStr> as From<Cow<'_, CStr>>> <Box<T> as From<T>> <Box<[T], A> as From<Vec<T, A>>> <Box<[T]> as From<&[T]>> <Box<[T]> as From<Cow<'_, [T]>>> <Box<[T]> as From<[T; N]>> and 4 others = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, CamError<<CS as embedded_hal::digital::v2::OutputPin>::Error, ES, EI>>>` for `Result<usize, Box<dyn core_error::Error>>`
尝试的解决写法
我试了三种写法,只有第三种能正常编译:
- 写法一(编译报错):
buf[rval] = reader.next_spi_byte().map_err(|e| Box::new(e))?;
对应的报错:
error[E0277]: `?` couldn't convert the error to `Box<dyn core_error::Error>` --> /home/thoth/src/rust-esp32-experiments/http-camera/webcam-applib/src/camera.rs:91:72 | 91 | buf[rval] = reader.next_spi_byte().map_err(|e| Box::new(e))?; | ^ the trait `From<Box<CamError<<CS as embedded_hal::digital::v2::OutputPin>::Error, ES, EI>>>` is not implemented for `Box<dyn core_error::Error>` | = note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait = help: the following other types implement trait `From<T>`: <Box<CStr> as From<&CStr>> <Box<CStr> as From<CString>> <Box<CStr> as From<Cow<'_, CStr>>> <Box<T> as From<T>> <Box<[T], A> as From<Vec<T, A>>> <Box<[T]> as From<&[T]>> <Box<[T]> as From<Cow<'_, [T]>>> <Box<[T]> as From<[T; N]>> and 4 others = note: required because of the requirements on the impl of `FromResidual<Result<Infallible, Box<CamError<<CS as embedded_hal::digital::v2::OutputPin>::Error, ES, EI>>>>` for `Result<usize, Box<dyn core_error::Error>>`
- 写法二(编译报错):
buf[rval] = reader.next_spi_byte().map_err(|e| Box::<dyn Error+'b>::new(e))?;
对应的报错:
error[E0599]: the function or associated item `new` exists for struct `Box<(dyn core_error::Error + 'b)>`, but its trait bounds were not satisfied --> /home/thoth/src/rust-esp32-experiments/http-camera/webcam-applib/src/camera.rs:92:81 | 92 | buf[rval] = reader.next_spi_byte().map_err(|e| Box::<dyn Error+'b>::new(e))?; | ^^^ function or associated item cannot be called on `Box<(dyn core_error::Error + 'b)>` due to unsatisfied trait bounds | ::: /home/thoth/.cargo/registry/src/github.com-1ecc6299db9ec823/core-error-0.0.0/src/error_trait.rs:19:1 | 19 | pub trait Error: Debug + Display { | -------------------------------- doesn't satisfy `dyn core_error::Error: Sized` | = note: the following trait bounds were not satisfied: `dyn core_error::Error: Sized`
- 写法三(编译通过):
buf[rval] = reader.next_spi_byte().map_err(|e| { let tmp: Box<dyn Error + 'b> = Box::new(e); tmp })?;
另外,isaactfa提供的写法也能正常编译:
buf[rval] = reader.next_spi_byte().map_err(|e| Box::new(e) as Box<dyn Error+'b>)?;
核心疑问
- 为什么第二种写法和第三种写法会有差异?
- 为什么
Box::<dyn Error+'b>::new(e)无法通过?操作符转换为Box<dyn Error+'b>,而as转换或者显式类型标注的写法可以?
解答
1. 第二种写法失败的原因
Box::<dyn Error + 'b>::new(e)调用的是Box的关联函数new,但这个函数要求泛型参数T必须满足**Sized trait**——因为new需要明确知道要分配的内存大小。而dyn Error + 'b是一个动态大小类型(DST),不满足Sized约束,所以编译器直接报错,无法完成函数调用。
2. 第三种写法与第二种的差异
第三种写法中,Box::new(e)首先创建了一个具体类型的Box<CamError<...>>,随后通过显式类型标注let tmp: Box<dyn Error + 'b>,触发了强制类型转换:将具体类型的Box转换为包含trait对象的Box。这个转换是合法的——因为CamError实现了Error trait,且Rust允许Box在具体类型和对应trait对象之间进行转换(本质是调整vtable指针与数据指针)。
3. as转换写法生效的原因
Box::new(e) as Box<dyn Error+'b>和第三种写法逻辑一致,都是显式触发从Box<CamError<...>>到Box<dyn Error+'b>的强制类型转换。这种写法直接告诉编译器我们需要的最终类型,跳过了?操作符依赖的From trait转换逻辑,因此能成功编译。
4. 写法一失败的原因
写法一中map_err(|e| Box::new(e))返回的是Box<CamError<...>>,随后?操作符会尝试通过From trait将该类型转换为函数返回的Box<dyn Error+'b>。但Rust并未为Box<dyn Error>实现From<Box<CamError>>(因为dyn Error是DST,无法自动推导这类转换),所以编译器报错From trait未实现。
内容的提问来源于stack exchange,提问作者Mutant Bob

