Rust错误:类型的大小`(dyn std::error::Error + 'static)`在编译时无法知道

xiozqbni  于 2023-06-30  发布在  其他
关注(0)|答案(2)|浏览(183)

我知道有无数类似的问题关于这个错误消息。但是我还没有找到我的错误的答案。首先,请允许我解释一下我的问题。这是编译器错误

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`

重要的函数签名有以下两个。

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(),
    // 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(),
};

为了完成,这里是我定义的结构,我假设我必须改变错误类型,但我不知道是什么。

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)
    }
}

我知道我在这里是代码转储,但我真的不知道如何解决这个问题。当然也有变通的办法,但到了这一点,我真的很想知道为什么这段代码不起作用,以及如何解决这个问题。
任何帮助都是非常感谢的。

p4rjhz4m

p4rjhz4m1#

问题

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,这很直观。正如this answer解释的那样,对于Box艾德Error来实现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()?尝试将您得到的错误转换为您想要返回的错误,并且有一个blanket impl,大致如下所示:

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

这使得任何实现Error的类型都可以实现。

手动解决方案

@alagner的答案当然是正确的,但是从C++继承了一些你在Rust中并不真正需要的东西,并省略了Rust提供的一些细节。您可以使用一个新类型定义一个更简单的 Package 器:

#[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
}

现在,您可以创建一个新的错误,如下所示:TimeError("Wrong length".into()),它就能正常工作了。
这样做的问题是,现在我们不能再使用?操作符了,因为Into<TimeError>没有实现任何其他我们可能想要冒泡的错误。如果我们能简单地写上

impl<E: Error> From<E> for TimeError {
    fn from(e: E) -> Self {
       Self(Box::new(e))
    }
}

但不幸的是,Rust的一致性规则不允许我们这样做。Error是为TimeError实现的,使得blanket impl包括impl From<TimeError> for TimeError,但是该impl已经存在(因为From<T>是为每个T实现的)。如果你仍然想使用?,你必须为你想传播的每个E手动实现From<E> for TimeError

impl From<std::num::ParseIntError> for TimeError {
    fn from(e: std::num::ParseIntError) -> Self {
        Self(Box::new(e))
    }
}

如果你最终需要传播一大堆不同的错误(在这个特定的情况下似乎不太可能,但无论如何),你可能想使用一个小的trait技巧来概括这一点。你可以定义你自己的Error的super trait,而TimeError * 没有 * 实现,为任何你想转换成TimeErrorE实现它,然后你可以为任何实现该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 impl:

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()
}
wn9m85ua

wn9m85ua2#

我不确定这是不是惯用的(可能不是),但你可以做的是创建自定义的错误类型,包含一个错误类型的Box,比如说像C++中的“私有实现”。inner和out都必须实现Error trait。这样你就可以将外部调用委托给内部调用:

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
}

相关问题