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

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

Rust Error: Size of Type `(dyn std::error::Error + 'static)` cannot be known at compilation time

问题

这是您提供的错误信息和相关代码的中文翻译:

  1. 我知道关于这个错误消息有无数类似的问题。然而,我还没有找到解决我的错误的答案。首先,允许我解释一下我的问题。这是编译器错误
  2. ```rust
  3. error[E0277]: 不能在编译时知道类型为`(dyn std::error::Error + 'static)`的值的大小
  4. --> src/category.rs:117:47
  5. |
  6. 117 | "Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
  7. | ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 不具备在编译时已知大小
  8. | |
  9. | 由此调用引入的绑定所需
  10. |
  11. = 帮助:对于`(dyn std::error::Error + 'static)`来实现`std::error::Error`,需要实现`Sized`特征
  12. = 帮助:对于`Box<T>`来实现`std::error::Error`,需要实现`std::error::Error`

重要的函数签名如下两个。

  1. pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
  2. where
  3. T::Err: Error + 'static,
  4. T: std::fmt::Display,

  1. fn get_value<T: FromStr>(value_str: &str) -> Option<T>
  2. where
  3. T::Err: Error + 'static,

据我理解,错误源于错误类型在编译时无法确定,我推测这是因为它使用了String类型作为错误消息?无论如何,让这个问题令人困惑的原因是,代码只对自定义结构体Time出现错误,而不对其他内置类型出现错误。

  1. let value: String = match type_str {
  2. "u32" => get_input::<u32>(help_msg, get_value::<u32>(default_value))?.to_string(),
  3. "i32" => get_input::<i32>(help_msg, get_value::<i32>(default_value))?.to_string(),
  4. "f32" => get_input::<f32>(help_msg, get_value::<f32>(default_value))?.to_string(),
  5. "bool" => get_input::<bool>(help_msg, get_value::<bool>(default_value))?.to_string(),
  6. "String" => get_input::<String>(help_msg, get_value::<String>(default_value))?.to_string(),
  7. // 这是导致编译错误的行
  8. "Time" => get_input::<Time>(help_msg, get_value::<Time>(default_value))?.to_string(),
  9. _ => "None".to_string(),
  10. };

为了完整起见,这是我定义的结构体,我推测我必须更改错误类型,但我不知道应该更改为什么。

  1. use std::error::Error;
  2. pub struct Time {
  3. hour: u8,
  4. minute: u8,
  5. }
  6. impl Time {
  7. pub fn new(hour: u8, minute: u8) -> Time {
  8. Time { hour, minute }
  9. }
  10. }
  11. impl std::str::FromStr for Time {
  12. type Err = Box<dyn Error + 'static>;
  13. fn from_str(s: &str) -> Result<Self, Self::Err> {
  14. let parts: Vec<&str> = s.split(':').collect();
  15. if parts.len() == 2 {
  16. let hour: u8 = parts[0].parse()?;
  17. let minute: u8 = parts[1].parse()?;
  18. Ok(Time::new(hour, minute))
  19. } else {
  20. Err("Invalid time format".into())
  21. }
  22. }
  23. }
  24. impl std::fmt::Display for Time {
  25. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  26. write!(f, "{:02}:{:02}", self.hour, self.minute)
  27. }
  28. }

我知道我在这里贴了大量代码,但我真的不知道如何修复这个问题。当然有解决方法,但到了这个时候,我真的想知道为什么这段代码不起作用以及如何修复这个问题。任何帮助都将不胜感激。

英文:

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

  1. error[E0277]: the size for values of type `(dyn std::error::Error + &#39;static)` cannot be known at compilation time
  2. --&gt; src/category.rs:117:47
  3. |
  4. 117 | &quot;Time&quot; =&gt; get_input::&lt;Time&gt;(help_msg, get_value::&lt;Time&gt;(default_value))?.to_string(),
  5. | ----------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn&#39;t have a size known at compile-time
  6. | |
  7. | required by a bound introduced by this call
  8. |
  9. = help: the trait `Sized` is not implemented for `(dyn std::error::Error + &#39;static)`
  10. = help: the trait `std::error::Error` is implemented for `Box&lt;T&gt;`
  11. = 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.

  1. 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;
  2. where
  3. T::Err: Error + &#39;static,
  4. T: std::fmt::Display,

and

  1. fn get_value&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
  2. where
  3. 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

  1. let value: String = match type_str {
  2. &quot;u32&quot; =&gt; get_input::&lt;u32&gt;(help_msg, get_value::&lt;u32&gt;(default_value))?.to_string(),
  3. &quot;i32&quot; =&gt; get_input::&lt;i32&gt;(help_msg, get_value::&lt;i32&gt;(default_value))?.to_string(),
  4. &quot;f32&quot; =&gt; get_input::&lt;f32&gt;(help_msg, get_value::&lt;f32&gt;(default_value))?.to_string(),
  5. &quot;bool&quot; =&gt; get_input::&lt;bool&gt;(help_msg, get_value::&lt;bool&gt;(default_value))?.to_string(),
  6. &quot;String&quot; =&gt; get_input::&lt;String&gt;(help_msg, get_value::&lt;String&gt;(default_value))?.to_string(),
  7. // This is the line that gives the compiler error
  8. &quot;Time&quot; =&gt; get_input::&lt;Time&gt;(help_msg, get_value::&lt;Time&gt;(default_value))?.to_string(),
  9. _ =&gt; &quot;None&quot;.to_string(),
  10. };

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.

  1. use std::error::Error;
  2. pub struct Time {
  3. hour: u8,
  4. minute: u8,
  5. }
  6. impl Time {
  7. pub fn new(hour: u8, minute: u8) -&gt; Time {
  8. Time { hour, minute }
  9. }
  10. }
  11. impl std::str::FromStr for Time {
  12. type Err = Box&lt;dyn Error + &#39;static&gt;;
  13. fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt; {
  14. let parts: Vec&lt;&amp;str&gt; = s.split(&#39;:&#39;).collect();
  15. if parts.len() == 2 {
  16. let hour: u8 = parts[0].parse()?;
  17. let minute: u8 = parts[1].parse()?;
  18. Ok(Time::new(hour, minute))
  19. } else {
  20. Err(&quot;Invalid time format&quot;.into())
  21. }
  22. }
  23. }
  24. impl std::fmt::Display for Time {
  25. fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;&#39;_&gt;) -&gt; std::fmt::Result {
  26. write!(f, &quot;{:02}:{:02}&quot;, self.hour, self.minute)
  27. }
  28. }

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

问题

  1. pub fn get_input<T: FromStr>(msg: &str, default_value: Option<T>) -> Result<T, Box<dyn Error>>
  2. where
  3. T::Err: Error + 'static,
  4. T: std::fmt::Display,

这里,T::Err: Error 要求 Box<dyn Error> 实现 Error,这在直观上是合理的。正如这个答案所解释的那样,然而,对于 Box 包装的 Error 来说,它需要是 Sized,而 trait 对象(即 dyn Error)则不是。

为什么一开始返回 Box<dyn Error> 如此方便呢?因为它允许您使用 ? 操作符将几乎任何类型的错误上升至上一级。

  1. let hour: u8 = parts[0].parse?;

会被展开成

  1. let hour: u8 = match parts[0] {
  2. Ok(v) => v,
  3. Err(e) => return Err(e.into()),
  4. };

注意到 e.into()? 试图将您获得的错误转换为您希望返回的错误,并且有一个大致如下的通用 impl

  1. impl<E: Error> From<E> for Box<dyn Error> { // ...

这使得对于任何实现 Error 的类型都可以实现这一点。

手动解决方案

@alagner 的答案确实朝着正确的方向发展,但从 C++ 继承了一些在 Rust 中不太需要的东西,并且省略了 Rust 提供的一些便利性。您可以使用一个简单的新类型来定义一个更简单的包装器:

  1. #[derive(Debug)]
  2. pub struct TimeError(Box<dyn Error>);
  3. impl fmt::Display for TimeError {
  4. fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
  5. // 委托格式化给底层错误
  6. self.0.fmt(f)
  7. }
  8. }
  9. impl Error for TimeError {
  10. // `Error` trait 上的方法已弃用,无需实现
  11. }

现在,您可以像这样创建一个新的错误:TimeError("Wrong length".into()),它将正常工作。

这种方法的问题是现在我们不能再使用 ? 操作符了,因为 Into<TimeError> 没有为我们可能想要上升的任何其他错误实现。如果仍然希望使用 ?,您将不得不为每个希望传播的 E 手动实现 From<E> for TimeError

  1. impl From<std::num::ParseIntError> for TimeError {
  2. fn from(e: std::num::ParseIntError) -> Self {
  3. Self(Box::new(e))
  4. }
  5. }

如果最终需要传播大量不同的错误(在这种特定情况下似乎不太可能,但无论如何),您可以使用一些小的 trait 技巧来进行泛化。您可以为任何希望能够转换为 TimeErrorE 实现您自己的 Error 超级 trait,然后您可以为任何实现该 trait 的类型进行通用 impl From

  1. trait ConvertibleError: Error {}
  2. impl<E: ConvertibleError + 'static> From<E> for TimeError {
  3. fn from(e: E) -> Self {
  4. Self(Box::new(e))
  5. }
  6. }
  7. impl ConvertibleError for std::num::ParseIntError {}

现在,如果需要传播另一种类型的错误 E,只需添加 impl ConvertibleError for E {}? 将再次工作,无需显式编写 impl From<E> for TimeError

或者,您可以编写一个简单的宏来为您编写 From 实现:

  1. macro_rules! impl_from_err {
  2. ($($ty:ty),*) => {
  3. $(
  4. impl From<$ty> for TimeError {
  5. fn from(e: $ty) -> Self {
  6. Self(Box::new(e))
  7. }
  8. }
  9. )*
  10. }
  11. }

并像这样调用它

  1. impl_from_err!(std::num::ParseIntError/*, OtherError, DifferentError, etc */);

便利解决方案

anyhow crate(还有其他错误处理库可用)提供了自己的 Error 类型,它工作方式类似于 Box<dyn Error>。它允许您上升几乎可能遇到的任何错误,并提供了一个方便的 anyhow::anyhow! 宏用于创建即席错误。

顺便说一下

您的 get_value 函数基本上只是将 Result 转换为 Option,标准库已经有一个方法来实现这一点:Result::ok

  1. fn get_value<T: FromStr>(value_str: &str) -> Option<T>
  2. where
  3. T::Err: std::error::Error
  4. {
  5. T::from_str(value_str).map_err(|e| eprintln!("{e:?}")).ok()
  6. }
英文:

The problem

  1. 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;
  2. where
  3. T::Err: Error + &#39;static,
  4. 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.

  1. let hour: u8 = parts[0].parse?;

desugars to

  1. let hour: u8 = match parts[0] {
  2. Ok(v) =&gt; v,
  3. Err(e) =&gt; return Err(e.into()),
  4. };

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

  1. 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:

  1. #[derive(Debug)]
  2. pub struct TimeError(Box&lt;dyn Error&gt;);
  3. impl fmt::Display for TimeError {
  4. fn fmt(&amp;self, f: &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; fmt::Result {
  5. // delegate formatting to the underlying error
  6. self.0.fmt(f)
  7. }
  8. }
  9. impl Error for TimeError {
  10. // the methods on the `Error` trait are deprecated and need not be implemented
  11. }

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

  1. impl&lt;E: Error&gt; From&lt;E&gt; for TimeError {
  2. fn from(e: E) -&gt; Self {
  3. Self(Box::new(e))
  4. }
  5. }

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:

  1. impl From&lt;std::num::ParseIntError&gt; for TimeError {
  2. fn from(e: std::num::ParseIntError) -&gt; Self {
  3. Self(Box::new(e))
  4. }
  5. }

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:

  1. trait ConvertibleError: Error {}
  2. impl&lt;E: ConvertibleError + &#39;static&gt; From&lt;E&gt; for TimeError {
  3. fn from(e: E) -&gt; Self {
  4. Self(Box::new(e))
  5. }
  6. }
  7. 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:

  1. macro_rules! impl_from_err {
  2. ($($ty:ty),*) =&gt; {
  3. $(
  4. impl From&lt;$ty&gt; for TimeError {
  5. fn from(e: $ty) -&gt; Self {
  6. Self(Box::new(e))
  7. }
  8. }
  9. )*
  10. }
  11. }

And call it like

  1. 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:

  1. fn get_value&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
  2. where
  3. T::Err: std::error::Error
  4. {
  5. T::from_str(value_str).map_err(|e| eprintln!(&quot;{e:?}&quot;)).ok()
  6. }

答案2

得分: 1

我绝不确定这是否符合惯用法(很可能不是),但您可以创建一个包含错误类型的自定义错误类型,例如来自C++的“私有实现”。内部和外部都必须实现Error特征。这样,您可以将外部调用委托给内部调用:

  1. use std::error::Error;
  2. use std::fmt;
  3. use std::str::FromStr;
  4. pub struct Time {
  5. hour: u8,
  6. minute: u8,
  7. }
  8. #[derive(Debug)]
  9. pub struct TimeError {
  10. err_impl : Box<dyn Error>
  11. }
  12. #[derive(Debug)]
  13. struct TimeErrorImpl {
  14. what : String
  15. }
  16. impl fmt::Display for TimeErrorImpl {
  17. fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
  18. write!(f, "{}", self.what)
  19. }
  20. }
  21. impl fmt::Display for TimeError {
  22. fn fmt(&self, f : &mut fmt::Formatter<'_>) -> fmt::Result {
  23. self.err_impl.fmt(f)
  24. }
  25. }
  26. impl Error for TimeErrorImpl {
  27. fn source(&self) -> Option<&(dyn Error + 'static)> { None }
  28. fn description(&self) -> &str { return &self.what; }
  29. }
  30. impl Error for TimeError {
  31. fn source(&self) -> Option<&(dyn Error + 'static)> { None }
  32. fn description(&self) -> &str { return self.err_impl.description(); }
  33. }
  34. impl Time {
  35. pub fn new(hour: u8, minute: u8) -> Time {
  36. Time { hour, minute }
  37. }
  38. }
  39. impl std::str::FromStr for Time {
  40. type Err = TimeError;
  41. fn from_str(s: &str) -> Result<Self, Self::Err> {
  42. let parts: Vec<&str> = s.split(':').collect();
  43. if parts.len() == 2 {
  44. let hour: u8;
  45. match parts[0].parse() {
  46. Ok(h) => hour = h,
  47. Err(er) => return Err(TimeError{err_impl:Box::new(er)})
  48. };
  49. let minute: u8;
  50. match parts[1].parse() {
  51. Ok(m) => minute = m,
  52. Err(er) => return Err(TimeError{err_impl:Box::new(er)})
  53. };
  54. Ok(Time::new(hour, minute))
  55. } else {
  56. Err(TimeError{err_impl:Box::new(TimeErrorImpl{what : "Wrong length".to_string()})})
  57. }
  58. }
  59. }
  60. impl std::fmt::Display for Time {
  61. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  62. write!(f, "{:02}:{:02}", self.hour, self.minute)
  63. }
  64. }
  65. fn get_value<T: FromStr>(value_str: &str) -> Option<T>
  66. where
  67. T::Err: Error + 'static
  68. {
  69. match T::from_str(value_str) {
  70. Ok(x) => return Some(x),
  71. Err(e) => println!("{}", e)
  72. };
  73. return None;
  74. }
  75. fn main() {
  76. let _: Option<i32> = get_value("32");
  77. let t: Option<Time> = get_value("ux:23:32"); //observe the message
  78. t.unwrap(); //crash here
  79. }
英文:

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:

  1. use std::error::Error;
  2. use std::fmt;
  3. use std::str::FromStr;
  4. pub struct Time {
  5. hour: u8,
  6. minute: u8,
  7. }
  8. #[derive(Debug)]
  9. pub struct TimeError {
  10. err_impl : Box&lt;dyn Error&gt;
  11. }
  12. #[derive(Debug)]
  13. struct TimeErrorImpl {
  14. what : String
  15. }
  16. impl fmt::Display for TimeErrorImpl {
  17. fn fmt(&amp;self, f : &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; fmt::Result {
  18. write!(f, &quot;{}&quot;, self.what)
  19. }
  20. }
  21. impl fmt::Display for TimeError {
  22. fn fmt(&amp;self, f : &amp;mut fmt::Formatter&lt;&#39;_&gt;) -&gt; fmt::Result {
  23. self.err_impl.fmt(f)
  24. }
  25. }
  26. impl Error for TimeErrorImpl {
  27. fn source(&amp;self) -&gt; Option&lt;&amp;(dyn Error + &#39;static)&gt; { None }
  28. fn description(&amp;self) -&gt; &amp;str { return &amp;self.what; }
  29. }
  30. impl Error for TimeError {
  31. fn source(&amp;self) -&gt; Option&lt;&amp;(dyn Error + &#39;static)&gt; { None }
  32. fn description(&amp;self) -&gt; &amp;str { return self.err_impl.description(); }
  33. }
  34. impl Time {
  35. pub fn new(hour: u8, minute: u8) -&gt; Time {
  36. Time { hour, minute }
  37. }
  38. }
  39. impl std::str::FromStr for Time {
  40. type Err = TimeError;
  41. fn from_str(s: &amp;str) -&gt; Result&lt;Self, Self::Err&gt; {
  42. let parts: Vec&lt;&amp;str&gt; = s.split(&#39;:&#39;).collect();
  43. if parts.len() == 2 {
  44. let hour: u8;
  45. match parts[0].parse() {
  46. Ok(h) =&gt; hour = h,
  47. Err(er) =&gt; return Err(TimeError{err_impl:Box::new(er)})
  48. };
  49. let minute: u8;
  50. match parts[1].parse() {
  51. Ok(m) =&gt; minute = m,
  52. Err(er) =&gt; return Err(TimeError{err_impl:Box::new(er)})
  53. };
  54. Ok(Time::new(hour, minute))
  55. } else {
  56. Err(TimeError{err_impl:Box::new(TimeErrorImpl{what : &quot;Wrong length&quot;.to_string()})})
  57. }
  58. }
  59. }
  60. impl std::fmt::Display for Time {
  61. fn fmt(&amp;self, f: &amp;mut std::fmt::Formatter&lt;&#39;_&gt;) -&gt; std::fmt::Result {
  62. write!(f, &quot;{:02}:{:02}&quot;, self.hour, self.minute)
  63. }
  64. }
  65. fn get_value&lt;T: FromStr&gt;(value_str: &amp;str) -&gt; Option&lt;T&gt;
  66. where
  67. T::Err: Error + &#39;static
  68. {
  69. match T::from_str(value_str) {
  70. Ok(x) =&gt; return Some(x),
  71. Err(e) =&gt; println!(&quot;{}&quot;, e)
  72. };
  73. return None;
  74. }
  75. fn main() {
  76. let _ : Option&lt;i32&gt; = get_value(&quot;32&quot;);
  77. let t : Option&lt;Time&gt; = get_value(&quot;ux:23:32&quot;); //observe the message
  78. t.unwrap(); //crash here
  79. }

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:

确定