英文:
Rust Error: Size of Type `(dyn std::error::Error + 'static)` cannot be known at compilation time
问题
这是您提供的错误信息和相关代码的中文翻译:
我知道关于这个错误消息有无数类似的问题。然而,我还没有找到解决我的错误的答案。首先,允许我解释一下我的问题。这是编译器错误
```rust
error[E0277]: 不能在编译时知道类型为`(dyn std::error::Error + 'static)`的值的大小
--> src/category.rs:117:47
|
117 | "Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
| ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 不具备在编译时已知大小
| |
| 由此调用引入的绑定所需
|
= 帮助:对于`(dyn std::error::Error + 'static)`来实现`std::error::Error`,需要实现`Sized`特征
= 帮助:对于`Box<T>`来实现`std::error::Error`,需要实现`std::error::Error`
重要的函数签名如下两个。
pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
where
T::Err: Error + 'static,
T: std::fmt::Display,
和
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: Error + 'static,
据我理解,错误源于错误类型在编译时无法确定,我推测这是因为它使用了String
类型作为错误消息?无论如何,让这个问题令人困惑的原因是,代码只对自定义结构体Time
出现错误,而不对其他内置类型出现错误。
let value: String = match type_str {
"u32" => get_input::<u32>(help_msg, get_value::<u32>(default_value))?.to_string(),
"i32" => get_input::<i32>(help_msg, get_value::<i32>(default_value))?.to_string(),
"f32" => get_input::<f32>(help_msg, get_value::<f32>(default_value))?.to_string(),
"bool" => get_input::<bool>(help_msg, get_value::<bool>(default_value))?.to_string(),
"String" => get_input::<String>(help_msg, get_value::<String>(default_value))?.to_string(),
// 这是导致编译错误的行
"Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
_ => "None".to_string(),
};
为了完整起见,这是我定义的结构体,我推测我必须更改错误类型,但我不知道应该更改为什么。
use std::error::Error;
pub struct Time {
hour: u8,
minute: u8,
}
impl Time {
pub fn new(hour: u8, minute: u8) -> Time {
Time { hour, minute }
}
}
impl std::str::FromStr for Time {
type Err = Box<dyn Error + 'static>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 2 {
let hour: u8 = parts[0].parse()?;
let minute: u8 = parts[1].parse()?;
Ok(Time::new(hour, minute))
} else {
Err("Invalid time format".into())
}
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
我知道我在这里贴了大量代码,但我真的不知道如何修复这个问题。当然有解决方法,但到了这个时候,我真的想知道为什么这段代码不起作用以及如何修复这个问题。任何帮助都将不胜感激。
英文:
I am aware that there are countless of similar questions regarding this error message. However I haven't found an answer to my error. First of all, allow me to explain my issue. This is the compiler error
error[E0277]: the size for values of type `(dyn std::error::Error + 'static)` cannot be known at compilation time
--> src/category.rs:117:47
|
117 | "Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
| ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
| |
| required by a bound introduced by this call
|
= help: the trait `Sized` is not implemented for `(dyn std::error::Error + 'static)`
= help: the trait `std::error::Error` is implemented for `Box<T>`
= note: required for `Box<(dyn std::error::Error + 'static)>` to implement `std::error::Error`
The important function signatures are the following two.
pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
where
T::Err: Error + 'static,
T: std::fmt::Display,
and
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: Error + 'static,
From what I understand, the error stems from the fact that the error type cannot be known at compile time,
I presume because it is using a String
type as the error message?
Either way, the reason this is so confusing is because the code errors only for the custom struct Time
,
but not for other built-ins
let value: String = match type_str {
"u32" => get_input::<u32>(help_msg, get_value::<u32>(default_value))?.to_string(),
"i32" => get_input::<i32>(help_msg, get_value::<i32>(default_value))?.to_string(),
"f32" => get_input::<f32>(help_msg, get_value::<f32>(default_value))?.to_string(),
"bool" => get_input::<bool>(help_msg, get_value::<bool>(default_value))?.to_string(),
"String" => get_input::<String>(help_msg, get_value::<String>(default_value))?.to_string(),
// This is the line that gives the compiler error
"Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
_ => "None".to_string(),
};
For completions sake, here is the struct I definied,
I presume that I have to change the error type but
I have no clue to what.
use std::error::Error;
pub struct Time {
hour: u8,
minute: u8,
}
impl Time {
pub fn new(hour: u8, minute: u8) -> Time {
Time { hour, minute }
}
}
impl std::str::FromStr for Time {
type Err = Box<dyn Error + 'static>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 2 {
let hour: u8 = parts[0].parse()?;
let minute: u8 = parts[1].parse()?;
Ok(Time::new(hour, minute))
} else {
Err("Invalid time format".into())
}
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
I am aware that I am code dumping here, but I really don't know how to fix this
issue at all. There are work arounds of course, but by this point, I really
want to know why this code is not working and how to fix the issue.
Any help is greatly appreciated.
答案1
得分: 3
问题
pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
where
T::Err: Error + 'static,
T: std::fmt::Display,
这里,T::Err: Error
要求 Box<dyn Error>
实现 Error
,这在直观上是合理的。正如这个答案所解释的那样,然而,对于 Box
包装的 Error
来说,它需要是 Sized
,而 trait 对象(即 dyn Error
)则不是。
为什么一开始返回 Box<dyn Error>
如此方便呢?因为它允许您使用 ?
操作符将几乎任何类型的错误上升至上一级。
let hour: u8 = parts[0].parse?;
会被展开成
let hour: u8 = match parts[0] {
Ok(v) => v,
Err(e) => return Err(e.into()),
};
注意到 e.into()
。?
试图将您获得的错误转换为您希望返回的错误,并且有一个大致如下的通用 impl
impl<E: Error> From<E> for Box<dyn Error> { // ...
这使得对于任何实现 Error
的类型都可以实现这一点。
手动解决方案
@alagner 的答案确实朝着正确的方向发展,但从 C++ 继承了一些在 Rust 中不太需要的东西,并且省略了 Rust 提供的一些便利性。您可以使用一个简单的新类型来定义一个更简单的包装器:
#[derive(Debug)]
pub struct TimeError(Box<dyn Error>);
impl fmt::Display for TimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// 委托格式化给底层错误
self.0.fmt(f)
}
}
impl Error for TimeError {
// `Error` trait 上的方法已弃用,无需实现
}
现在,您可以像这样创建一个新的错误:TimeError("Wrong length".into())
,它将正常工作。
这种方法的问题是现在我们不能再使用 ?
操作符了,因为 Into<TimeError>
没有为我们可能想要上升的任何其他错误实现。如果仍然希望使用 ?
,您将不得不为每个希望传播的 E
手动实现 From<E> for TimeError
:
impl From<std::num::ParseIntError> for TimeError {
fn from(e: std::num::ParseIntError) -> Self {
Self(Box::new(e))
}
}
如果最终需要传播大量不同的错误(在这种特定情况下似乎不太可能,但无论如何),您可以使用一些小的 trait 技巧来进行泛化。您可以为任何希望能够转换为 TimeError
的 E
实现您自己的 Error
超级 trait,然后您可以为任何实现该 trait 的类型进行通用 impl From
:
trait ConvertibleError: Error {}
impl<E: ConvertibleError + 'static> From<E> for TimeError {
fn from(e: E) -> Self {
Self(Box::new(e))
}
}
impl ConvertibleError for std::num::ParseIntError {}
现在,如果需要传播另一种类型的错误 E
,只需添加 impl ConvertibleError for E {}
,?
将再次工作,无需显式编写 impl From<E> for TimeError
。
或者,您可以编写一个简单的宏来为您编写 From
实现:
macro_rules! impl_from_err {
($($ty:ty),*) => {
$(
impl From<$ty> for TimeError {
fn from(e: $ty) -> Self {
Self(Box::new(e))
}
}
)*
}
}
并像这样调用它
impl_from_err!(std::num::ParseIntError/*, OtherError, DifferentError, etc */);
便利解决方案
anyhow
crate(还有其他错误处理库可用)提供了自己的 Error
类型,它工作方式类似于 Box<dyn Error>
。它允许您上升几乎可能遇到的任何错误,并提供了一个方便的 anyhow::anyhow!
宏用于创建即席错误。
顺便说一下
您的 get_value
函数基本上只是将 Result
转换为 Option
,标准库已经有一个方法来实现这一点:Result::ok
:
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: std::error::Error
{
T::from_str(value_str).map_err(|e| eprintln!("{e:?}")).ok()
}
英文:
The problem
pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
where
T::Err: Error + 'static,
T: std::fmt::Display,
Here, T::Err: Error
requires Box<dyn Error>
to implement Error
which makes intuitive sense. As this answer explains, though, for a Box
ed Error
to implement Error
, it needs to be Sized
, which trait objects (i.e. dyn Error
) aren't.
Why is returning Box<dyn Error>
so convenient in the first place? Because it allows you to use the ?
operator to bubble up almost any kind of error.
let hour: u8 = parts[0].parse?;
desugars to
let hour: u8 = match parts[0] {
Ok(v) => v,
Err(e) => return Err(e.into()),
};
Note the e.into()
. ?
tries to convert the error you get into the error you'd like to return and there's a blanket impl that looks roughly like
impl<E: Error> From<E> for Box<dyn Error> { // ...
that makes this possible for any type that implements Error
.
The manual solution
@alagner's answer certainly goes in the right direction but inherits some cruft from C++ you don't really need in Rust and omits some niceties that Rust provides. You can define a much simpler wrapper using a single new type:
#[derive(Debug)]
pub struct TimeError(Box<dyn Error>);
impl fmt::Display for TimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// delegate formatting to the underlying error
self.0.fmt(f)
}
}
impl Error for TimeError {
// the methods on the `Error` trait are deprecated and need not be implemented
}
Now you can make a new error like this: TimeError("Wrong length".into())
and it'll just work.
The problem with this is that now we can't use the ?
operator anymore, because Into<TimeError>
isn't implemented for any of the other errors we might like to bubble up. It would be nice if we could simply write
impl<E: Error> From<E> for TimeError {
fn from(e: E) -> Self {
Self(Box::new(e))
}
}
but unfortunately, Rust's coherence rules won't let us. Error
is implemented for TimeError
so that blanket impl includes a impl From<TimeError> for TimeError
but that impl already exists (because From<T>
is implemented for every T
). If you still want to use the ?
, you'll have to manually implement From<E> for TimeError
for every E
that you want to propagate:
impl From<std::num::ParseIntError> for TimeError {
fn from(e: std::num::ParseIntError) -> Self {
Self(Box::new(e))
}
}
If you end up needing to propagate a whole bunch of different errors (seems unlikely in this particular case, but anyways), you might want to generalize this using a little trait trick. You can define your own super trait of Error
that TimeError
doesn't implement, implement it for any E
you want to be able to convert into a TimeError
, then you can blanket impl From
for any type that implements that trait:
trait ConvertibleError: Error {}
impl<E: ConvertibleError + 'static> From<E> for TimeError {
fn from(e: E) -> Self {
Self(Box::new(e))
}
}
impl ConvertibleError for std::num::ParseIntError {}
Now, if you need to be able to propagate another kind of error E
, you just need to add impl ConvertibleError for E {}
and ?
will work again, no need to explicitly write impl From<E> for TimeError
.
Alternatively you can write a simple macro to write the From
impl for you:
macro_rules! impl_from_err {
($($ty:ty),*) => {
$(
impl From<$ty> for TimeError {
fn from(e: $ty) -> Self {
Self(Box::new(e))
}
}
)*
}
}
And call it like
impl_from_err!(std::num::ParseIntError/*, OtherError, DifferentError, etc */);
The convenient solution
The anyhow
crate (other error handling libraries are available) provides its own Error
type that works much like Box<dyn Error>
. It allows you to bubble up pretty much any error you might encounter and it provides a convenient anyhow::anyhow!
macro to create ad-hoc errors.
An aside
Your get_value
function does basically nothing but turn a Result
into an Option
, which the standard library already has a method for: Result::ok
:
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: std::error::Error
{
T::from_str(value_str).map_err(|e| eprintln!("{e:?}")).ok()
}
答案2
得分: 1
我绝不确定这是否符合惯用法(很可能不是),但您可以创建一个包含错误类型的自定义错误类型,例如来自C++的“私有实现”。内部和外部都必须实现Error特征。这样,您可以将外部调用委托给内部调用:
use std::error::Error;
use std::fmt;
use std::str::FromStr;
pub struct Time {
hour: u8,
minute: u8,
}
#[derive(Debug)]
pub struct TimeError {
err_impl : Box<dyn Error>
}
#[derive(Debug)]
struct TimeErrorImpl {
what : String
}
impl fmt::Display for TimeErrorImpl {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.what)
}
}
impl fmt::Display for TimeError {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
self.err_impl.fmt(f)
}
}
impl Error for TimeErrorImpl {
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
fn description(&self) -> &str { return &self.what; }
}
impl Error for TimeError {
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
fn description(&self) -> &str { return self.err_impl.description(); }
}
impl Time {
pub fn new(hour: u8, minute: u8) -> Time {
Time { hour, minute }
}
}
impl std::str::FromStr for Time {
type Err = TimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 2 {
let hour: u8;
match parts[0].parse() {
Ok(h) => hour = h,
Err(er) => return Err(TimeError{err_impl:Box::new(er)})
};
let minute: u8;
match parts[1].parse() {
Ok(m) => minute = m,
Err(er) => return Err(TimeError{err_impl:Box::new(er)})
};
Ok(Time::new(hour, minute))
} else {
Err(TimeError{err_impl:Box::new(TimeErrorImpl{what : "Wrong length".to_string()})})
}
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: Error + 'static
{
match T::from_str(value_str) {
Ok(x) => return Some(x),
Err(e) => println!("{}", e)
};
return None;
}
fn main() {
let _: Option<i32> = get_value("32");
let t: Option<Time> = get_value("ux:23:32"); //observe the message
t.unwrap(); //crash here
}
英文:
I'm by no means sure this is idiomatic (it's probably not), but what you can do is create custom Error type containing a Box of error types, say sth like a "private implementation" known from C++. Both inner and out have to implement Error trait. This way you can delegate the outer calls to the inner one:
use std::error::Error;
use std::fmt;
use std::str::FromStr;
pub struct Time {
hour: u8,
minute: u8,
}
#[derive(Debug)]
pub struct TimeError {
err_impl : Box<dyn Error>
}
#[derive(Debug)]
struct TimeErrorImpl {
what : String
}
impl fmt::Display for TimeErrorImpl {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.what)
}
}
impl fmt::Display for TimeError {
fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
self.err_impl.fmt(f)
}
}
impl Error for TimeErrorImpl {
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
fn description(&self) -> &str { return &self.what; }
}
impl Error for TimeError {
fn source(&self) -> Option<&(dyn Error + 'static)> { None }
fn description(&self) -> &str { return self.err_impl.description(); }
}
impl Time {
pub fn new(hour: u8, minute: u8) -> Time {
Time { hour, minute }
}
}
impl std::str::FromStr for Time {
type Err = TimeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split(':').collect();
if parts.len() == 2 {
let hour: u8;
match parts[0].parse() {
Ok(h) => hour = h,
Err(er) => return Err(TimeError{err_impl:Box::new(er)})
};
let minute: u8;
match parts[1].parse() {
Ok(m) => minute = m,
Err(er) => return Err(TimeError{err_impl:Box::new(er)})
};
Ok(Time::new(hour, minute))
} else {
Err(TimeError{err_impl:Box::new(TimeErrorImpl{what : "Wrong length".to_string()})})
}
}
}
impl std::fmt::Display for Time {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:02}:{:02}", self.hour, self.minute)
}
}
fn get_value<T: FromStr>(value_str: &str) -> Option<T>
where
T::Err: Error + 'static
{
match T::from_str(value_str) {
Ok(x) => return Some(x),
Err(e) => println!("{}", e)
};
return None;
}
fn main() {
let _ : Option<i32> = get_value("32");
let t : Option<Time> = get_value("ux:23:32"); //observe the message
t.unwrap(); //crash here
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论