Rust错误:类型`(dyn std::error::Error + ‘static)`的大小无法在编译时确定。

huangapple go评论84阅读模式
英文:

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 + &#39;static)` cannot be known at compilation time
   --&gt; src/category.rs:117:47
    |
117 |         &quot;Time&quot; =&gt; get_input::&lt;Time&gt;(help_msg, get_value::&lt;Time&gt;(default_value))?.to_string(),
    |                   -----------------           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn&#39;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 + &#39;static)`
    = help: the trait `std::error::Error` is implemented for `Box&lt;T&gt;`
    = note: required for `Box&lt;(dyn std::error::Error + &#39;static)&gt;` to implement `std::error::Error`

The important function signatures are the following two.

pub fn get_input&lt;T: FromStr&gt;(msg: &amp;str, default_value: Option&lt;T&gt;) -&gt; Result&lt;T, Box&lt;dyn Error&gt;&gt;
where
    T::Err: Error + &#39;static,
    T: std::fmt::Display,

and

fn get_value&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
where
    T::Err: Error + &#39;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 {
    &quot;u32&quot; =&gt; get_input::&lt;u32&gt;(help_msg, get_value::&lt;u32&gt;(default_value))?.to_string(),
    &quot;i32&quot; =&gt; get_input::&lt;i32&gt;(help_msg, get_value::&lt;i32&gt;(default_value))?.to_string(),
    &quot;f32&quot; =&gt; get_input::&lt;f32&gt;(help_msg, get_value::&lt;f32&gt;(default_value))?.to_string(),
    &quot;bool&quot; =&gt; get_input::&lt;bool&gt;(help_msg, get_value::&lt;bool&gt;(default_value))?.to_string(),
    &quot;String&quot; =&gt; get_input::&lt;String&gt;(help_msg, get_value::&lt;String&gt;(default_value))?.to_string(),
    // This is the line that gives the compiler error
    &quot;Time&quot; =&gt; get_input::&lt;Time&gt;(help_msg, get_value::&lt;Time&gt;(default_value))?.to_string(),
    _ =&gt; &quot;None&quot;.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) -&gt; Time {
        Time { hour, minute }
    }
}

impl std::str::FromStr for Time {
    type Err = Box&lt;dyn Error + &#39;static&gt;;
    fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt; {
        let parts: Vec&lt;&amp;str&gt; = s.split(&#39;:&#39;).collect();
        if parts.len() == 2 {
            let hour: u8 = parts[0].parse()?;
            let minute: u8 = parts[1].parse()?;
            Ok(Time::new(hour, minute))
        } else {
            Err(&quot;Invalid time format&quot;.into())
        }
    }
}

impl std::fmt::Display for Time {
    fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;&#39;_&gt;) -&gt; std::fmt::Result {
        write!(f, &quot;{:02}:{:02}&quot;, 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 技巧来进行泛化。您可以为任何希望能够转换为 TimeErrorE 实现您自己的 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&lt;T: FromStr&gt;(msg: &amp;str, default_value: Option&lt;T&gt;) -&gt; Result&lt;T, Box&lt;dyn Error&gt;&gt;
where
    T::Err: Error + &#39;static,
    T: std::fmt::Display,

Here, T::Err: Error requires Box&lt;dyn Error&gt; to implement Error which makes intuitive sense. As this answer explains, though, for a Boxed Error to implement Error, it needs to be Sized, which trait objects (i.e. dyn Error) aren't.

Why is returning Box&lt;dyn Error&gt; 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) =&gt; v,
    Err(e) =&gt; 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&lt;E: Error&gt; From&lt;E&gt; for Box&lt;dyn Error&gt; { // ...

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&lt;dyn Error&gt;);

impl fmt::Display for TimeError {
    fn fmt(&amp;self, f: &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; 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(&quot;Wrong length&quot;.into()) and it'll just work.

The problem with this is that now we can't use the ? operator anymore, because Into&lt;TimeError&gt; 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&lt;E: Error&gt; From&lt;E&gt; for TimeError {
    fn from(e: E) -&gt; 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&lt;TimeError&gt; for TimeError but that impl already exists (because From&lt;T&gt; is implemented for every T). If you still want to use the ?, you'll have to manually implement From&lt;E&gt; for TimeError for every E that you want to propagate:

impl From&lt;std::num::ParseIntError&gt; for TimeError {
    fn from(e: std::num::ParseIntError) -&gt; 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&lt;E: ConvertibleError + &#39;static&gt; From&lt;E&gt; for TimeError {
    fn from(e: E) -&gt; 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&lt;E&gt; for TimeError.

Alternatively you can write a simple macro to write the From impl for you:

macro_rules! impl_from_err {
    ($($ty:ty),*) =&gt; {
        $(
            impl From&lt;$ty&gt; for TimeError {
                fn from(e: $ty) -&gt; 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&lt;dyn Error&gt;. 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&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
where
    T::Err: std::error::Error
{
    T::from_str(value_str).map_err(|e| eprintln!(&quot;{e:?}&quot;)).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&lt;dyn Error&gt;
}


#[derive(Debug)]
struct TimeErrorImpl {
    what : String
}

impl fmt::Display for TimeErrorImpl {
    fn fmt(&amp;self, f : &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; fmt::Result {
        write!(f, &quot;{}&quot;, self.what)
    }
}

impl fmt::Display for TimeError {
    fn fmt(&amp;self, f : &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; fmt::Result {
        self.err_impl.fmt(f)
    }
}


impl Error for TimeErrorImpl {
    fn source(&amp;self) -&gt; Option&lt;&amp;(dyn Error + &#39;static)&gt; { None }
    fn description(&amp;self) -&gt; &amp;str { return &amp;self.what; } 
}

impl Error for TimeError {
    fn source(&amp;self) -&gt; Option&lt;&amp;(dyn Error + &#39;static)&gt; { None }
    fn description(&amp;self) -&gt; &amp;str { return self.err_impl.description(); } 
}


impl Time {
    pub fn new(hour: u8, minute: u8) -&gt; Time {
        Time { hour, minute }
    }
}

impl std::str::FromStr for Time {
    type Err = TimeError;
    fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt; {
        let parts: Vec&lt;&amp;str&gt; = s.split(&#39;:&#39;).collect();
        if parts.len() == 2 {
            let hour: u8;
            match   parts[0].parse() {
                Ok(h) =&gt; hour = h,
                Err(er) =&gt; return Err(TimeError{err_impl:Box::new(er)})
            };
            let minute: u8;

            match   parts[1].parse() {
                Ok(m) =&gt; minute = m,
                Err(er) =&gt; return Err(TimeError{err_impl:Box::new(er)})
            };
            Ok(Time::new(hour, minute))
        } else {
            Err(TimeError{err_impl:Box::new(TimeErrorImpl{what : &quot;Wrong length&quot;.to_string()})})
        }
    }
}

impl std::fmt::Display for Time {
    fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;&#39;_&gt;) -&gt; std::fmt::Result {
        write!(f, &quot;{:02}:{:02}&quot;, self.hour, self.minute)
    }
}

fn get_value&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
where
    T::Err: Error + &#39;static
{
    match T::from_str(value_str) {
        Ok(x) =&gt; return Some(x),
        Err(e) =&gt; println!(&quot;{}&quot;, e)
    };
    return None;
}


fn main() {
    let _ : Option&lt;i32&gt; = get_value(&quot;32&quot;);
    let t : Option&lt;Time&gt; = get_value(&quot;ux:23:32&quot;); //observe the message
    t.unwrap(); //crash here
}

huangapple
  • 本文由 发表于 2023年6月27日 19:27:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76564392.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定