使用包含在 Option 枚举中的 trait-bounded 参数创建函数时出错

Error in creating a function with a trait-bounded parameter wrapped in Option enum

我想为我的 MyError 枚举(其变体是我程序中的不同错误类型)创建一个方法,returns 一个 String 值描述了给定的 MyError 变体。例如:

pub enum MyError {
    Error1,
    Error2,
}

impl MyError {
    pub fn to_str(&self) -> String {
        match self {
            Error1 => format!("Error1: bla bla bla"),
            Error2 => format!("Error2: na na na"),
        }
    }
}

一切都很好,但问题是,我有一个新的错误变体(比如 Error3),它必须在方法中将参数传递给它的 format!() 宏,就像这样:

Error3 => format!("la la la {:?}", arg),

这个参数可以是任何类型,只要它可以派生出 Debug 特征。所以我的解决方案是

pub enum MyError {
    Error1,
    Error2,
    Error3
}

impl MyError {
    pub fn to_str(&self, arg: Option<&impl fmt::Debug>) -> String {
        match self {
            Error1 => format!("bla bla bla"),
            Error2 => format!("na na na"),
            Error3 => format!("la la la {:?}", arg),
        }
    }
}

我在 Option 中包装了 trait-bounded 参数,因为 MyError 的某些变体不需要它(例如 Error1)。这适用于 Error3 变体,我可以在没有任何编译错误的情况下执行以下操作:

eprintln!("{}", MyError::Error3.to_str(Some(vec![1, 2, 3])));

它打印 Error3 的相关错误消息。但是当我尝试将该方法用于不需要额外参数的其他变体时,例如打电话

eprintln!("{}", MyError::Error1.to_str(None));

它returns下面的编译错误:

type annotations needed

cannot infer type for type parameter `impl fmt::Debug` declared on the associated function `to_str`rustc(E0282)

为什么编译器不能在这里推断出 None 的类型?

您需要指定具体类型,即使是 None。像这样:

let n: Option<&Vec<usize>> = None;
eprintln!("{}", MyError::Error1.to_str(n));

即使函数(方法)只指定了特征边界,而不是具体类型,编译器需要确切地知道调用者传递给该函数的每个值的类型。

当然,您可以使用不同类型的值调用该函数(但每个类型都需要清楚并且必须遵守特征界限)。一个例子:

pub fn main() {
    let v1 = vec![1, 2, 3];
    let v2 = "A str here";
    eprintln!("{}", MyError::Error3.to_str(Some(&v1)));
    eprintln!("{}", MyError::Error3.to_str(Some(&v2)));
    let n: Option<&Vec<usize>> = None;
    eprintln!("{}", MyError::Error1.to_str(n));
    let n2: Option<&Vec<String>> = None;
    eprintln!("{}", MyError::Error1.to_str(n2));
}

您可以了解更多信息here

它无法推断出这一点的原因可能是由于一些烦人的边缘情况。主要的是确定传递的枚举的大小。枚举类型的大小取决于其最大变体的大小。你可以用 assert_neq(std::mem::size_of::<Option<i32>>(), std::mem::size_of::<Option<i8>>()); 看到这个。在不知道它包含什么具体类型的情况下,我们无法知道函数在传递参数时应该使用多少space。

最简单的方法可能是允许用户将具体类型指定为类型参数。即使您传入 None,您仍然可以指定具体类型作为替代(例如:error.to_str::<()>(None))。

impl MyError {
    pub fn to_str<D: fmt::Debug>(&self, arg: Option<&D>) -> String {
        // etc.
    }
}

否则,我建议分离方法以完全避免该问题。

impl MyError {
    pub fn to_str(&self) -> String {
        match self {
            Error1 => format!("bla bla bla"),
            Error2 => format!("na na na"),
            Error3 => format!("la la la"),
        }
    }

    pub fn to_str_with_context(&self, arg: &impl fmt::Debug) -> String {
        match self {
            Error1 => format!("bla bla bla"),
            Error2 => format!("na na na"),
            Error3 => format!("la la la {:?}", arg),
        }
    }
}

除此之外,我只能提供一般编码技巧。 我的第一直觉是您可能会发现使用显示包装器更方便。

pub struct MyErrorWithContext<'a, D> {
    err: &'a MyError,
    context: Option<&'a D>,
}

impl<'a, D: fmt::Debug> fmt::Display for MyErrorWithContext<'a, D> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self.err {
            Error1 => write!(f, "bla bla bla"),
            Error2 => write!(f, "na na na"),
            Error3 => write!(f, "la la la {:?}", &self.context),
        }
    }
}

impl MyError {
    pub fn display<D: Debug>(&self, context: Option<&D>) -> MyErrorWithContext<D> {
        MyErrorWithContext {
            err: self,
            context,
        }
    }
}


println!("Error: {}", error.display(Some(&foo)));