在Rust中返回结果而不是结果的最佳做法?

在Rust中返回结果而不是结果的最佳做法?,rust,Rust,有这种风格的结果可以练习吗 fn a->Result对于简单用例,像Result这样的不透明错误类型对于简单用例,像Result这样的不透明错误类型简言之:不,这不好。字符串作为错误会丢弃有关详细信息和原因的信息,使错误对调用方毫无用处,因为调用方将无法检查错误并可能从中恢复 如果您只需要用一些东西填充错误参数,请创建一个单元结构。它并没有多大用处,但也不像字符串那个样随意。您可以轻松区分foo::SomeError和bar::SomeError [衍生博客] 发布结构SomeError;//没

有这种风格的结果可以练习吗


fn a->Result对于简单用例,像Result这样的不透明错误类型对于简单用例,像Result这样的不透明错误类型简言之:不,这不好。字符串作为错误会丢弃有关详细信息和原因的信息,使错误对调用方毫无用处,因为调用方将无法检查错误并可能从中恢复

如果您只需要用一些东西填充错误参数,请创建一个单元结构。它并没有多大用处,但也不像字符串那个样随意。您可以轻松区分foo::SomeError和bar::SomeError

[衍生博客] 发布结构SomeError;//没有字段。 如果可以枚举错误变量,请使用enum。 有时,在其中包含其他错误也很有用

[衍生博客] 发布枚举密码错误{ 空的 托肖特, 诺迪吉特人, NoLetters, 医院专科 } [衍生博客] 发布枚举ConfigLoadError{ 无效价值观, 反序列化ErrorSerde::de::Error, IoErrorstd::io::Error, } 没有人阻止你使用structs。 当您有意对调用方隐藏某些信息时,它们特别有用,而枚举的变体总是具有公共可见性。例如,调用方与错误消息无关,但可以使用kind来处理它:

发布枚举注册ErrorKind{ InvalidName{错误的字符:usize}, 非唯一名称, 懦夫, DatabaseErrordb::Error, } [衍生博客] 发布结构注册错误{ 消息:字符串,//私有字段 发布种类:RegistrationErrorKind,//公共字段 } impl Error-存在类型-在这里没有意义。如果这是您的意图,您不能在错误位置返回不同的错误类型。和字符串一样,不透明错误也没有多大用处

std::error::error trait确保您的SomeError类型具有std::fmt:{Display,Debug}的实现,用于向用户和开发人员显示错误,并相应地提供一些有用的方法,如source This返回此错误的原因;是,向下,向下,向下,向下。最后4个用于错误类型擦除

错误类型擦除

错误类型擦除有其折衷之处,但也值得一提

在编写高级应用程序代码时,它也特别有用。但是对于库,在决定使用这种方法之前,您应该三思而后行,因为它会使您的库在“no_std”情况下无法使用

假设您有一个具有非平凡逻辑的函数,它可以返回某些错误类型的值,而不是一种。在这种情况下,您可以使用但不要滥用错误类型擦除:

use std::error::Error;
use std::fmt;
use std::fs;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::net::Ipv4Addr
use std::path::Path;

// Error for case where file contains '127.0.0.1'
#[derive(Debug)]
pub struct AddressIsLocalhostError;

// Display implementation is required for std::error::Error.
impl fmt::Display for AddressIsLocalhostError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Address is localhost")
    }
}

impl Error for AddresIsLocalhostError {} // Defaults are okay here.

// Now we have a function that takes a path and returns 
// non-localhost Ipv4Addr on success.
// On fail it can return either of IoError, AddrParseError or AddressIsLocalhostError.
fn non_localhost_ipv4_from_file(path: &Path) -> Result<Ipv4Addr, Box<dyn Error + 'static>> {
    // Opening and reading file may cause IoError.
    // ? operator will automatically convert it to Box<dyn Error + 'static>. 
    // (via From trait implementation)
    // This way concrete type of error is "erased": we don't know what's
    // in a box, in fact it's kind of black box now, but we still can call
    // methods that Error trait provides.
    let content = fs::read_to_string(path)?;

    // Parsing Ipv4Addr from string [slice] 
    // may cause another error: AddressParseError.
    // And ? will convert it to to the same type: Box<dyn Error + 'static>
    let addr: Ipv4Addr = content.parse()?;

    if addr == Ipv4Add::new(127, 0, 0, 1) {
        // Here we perform manual conversion 
        // from AddressIsLocalhostError 
        // to Box<dyn Error + 'static> and return error.
        return Err(AddressIsLocalhostError.into());
    }

    // Everyhing is okay, returning addr.
    Ok(Ipv4Addr)
}


fn main() {
    // Let's try to use our function.
    let maybe_address = non_localhost_ipv4_from_file(
        "sure_it_contains_localhost.conf"
    );

    // Let's see what kind of magic Error trait provides!
    match maybe_address {
        // Print address on success.
        Ok(addr) => println!("File was containing address: {}", addr),
        Err(err) => {
            // We sure can just print this error with.
            // println!("{}", err.as_ref());
            // Because Error implementation implies Display implementation.
            // But let's imagine we want to inspect error.

            // Here deref coercion implicitly converts
            // `&Box<dyn Error>` to `&dyn Error`.
            // And downcast_ref tries to convert this &dyn Error
            // back to &IoError, returning either
            // Some(&IoError) or none
            if Some(err) = err.downcast_ref::<IoError>() {
                println!("Unfortunately, IO error occured: {}", err)
            }

            // There's also downcast_mut, which does the same, but gives us
            // mutable reference.
            if Some(mut err) = err.downcast_mut::<AddressParseError>() {
                // Here we can mutate err. But we'll only print it.
                println!(
                    "Unfortunately, what file was cantaining, \
                     was not in fact an ipv4 address: {}",
                    err
                );
            }

            // Finally there's 'is' and 'downcast'.
            // 'is' comapres "erased" type with some concrete type.
            if err.is::<AddressIsLocalhostError>() {
               // 'downcast' tries to convert Box<dyn Error + 'static>
               // to box with value of some concrete type.
               // Here - to Box<AddressIsLocalhostError>.
               let err: Box<AddressIsLocalhostError> = 
                   Error::downcast(err).unwrap();
            }
        }
    };
}
总而言之:我应该说,错误必须向调用者提供有用的信息,除了能够仅仅显示它们之外,所以它们不应该是字符串。错误必须至少实现错误,以便在所有板条箱中保持更不一致的错误处理经验。其余的一切取决于具体情况

Caio alredy提到了铁锈书

但这些链接也可能有用:


简言之:不,这不好。字符串作为错误会丢弃有关详细信息和原因的信息,使错误对调用方毫无用处,因为调用方将无法检查错误并可能从中恢复

如果您只需要用一些东西填充错误参数,请创建一个单元结构。它并没有多大用处,但也不像字符串那个样随意。您可以轻松区分foo::SomeError和bar::SomeError

[衍生博客] 发布结构SomeError;//没有字段。 如果可以枚举错误变量,请使用enum。 有时,在其中包含其他错误也很有用

[衍生博客] 发布枚举密码错误{ 空的 托肖特, 诺迪吉特人, NoLetters, 医院专科 } [衍生博客] 发布枚举ConfigLoadError{ 无效价值观, 反序列化ErrorSerde::de::Error, IoErrorstd::io::Error, } 没有人阻止你使用structs。 当您有意对调用方隐藏某些信息时,它们特别有用,而枚举的变体总是具有公共可见性。例如,调用方与错误消息无关,但可以使用kind来处理它:

发布枚举注册ErrorKind{ InvalidName{错误的字符:usize}, 非唯一名称, 懦夫, DatabaseErrordb::Error, } [衍生博客] 发布结构注册错误{ 消息:字符串,//私有字段 发布种类:RegistrationErrorKind,//公共字段 } impl Error-存在类型-在这里没有意义。如果这是您的意图,您不能在错误位置返回不同的错误类型。和字符串一样,不透明错误也没有多大用处

std::error::error trait确保您的SomeError类型具有std::fmt:{Display,Debug}的实现,用于向用户和开发人员显示错误,并相应地提供一些有用的方法,如source This返回此错误的原因;是,向下,向下,向下,向下。 最后4个用于错误类型擦除

错误类型擦除

错误类型擦除有其折衷之处,但也值得一提

在编写高级应用程序代码时,它也特别有用。但是对于库,在决定使用这种方法之前,您应该三思而后行,因为它会使您的库在“no_std”情况下无法使用

假设您有一个具有非平凡逻辑的函数,它可以返回某些错误类型的值,而不是一种。在这种情况下,您可以使用但不要滥用错误类型擦除:

use std::error::Error;
use std::fmt;
use std::fs;
use std::io::Error as IoError;
use std::net::AddrParseError;
use std::net::Ipv4Addr
use std::path::Path;

// Error for case where file contains '127.0.0.1'
#[derive(Debug)]
pub struct AddressIsLocalhostError;

// Display implementation is required for std::error::Error.
impl fmt::Display for AddressIsLocalhostError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Address is localhost")
    }
}

impl Error for AddresIsLocalhostError {} // Defaults are okay here.

// Now we have a function that takes a path and returns 
// non-localhost Ipv4Addr on success.
// On fail it can return either of IoError, AddrParseError or AddressIsLocalhostError.
fn non_localhost_ipv4_from_file(path: &Path) -> Result<Ipv4Addr, Box<dyn Error + 'static>> {
    // Opening and reading file may cause IoError.
    // ? operator will automatically convert it to Box<dyn Error + 'static>. 
    // (via From trait implementation)
    // This way concrete type of error is "erased": we don't know what's
    // in a box, in fact it's kind of black box now, but we still can call
    // methods that Error trait provides.
    let content = fs::read_to_string(path)?;

    // Parsing Ipv4Addr from string [slice] 
    // may cause another error: AddressParseError.
    // And ? will convert it to to the same type: Box<dyn Error + 'static>
    let addr: Ipv4Addr = content.parse()?;

    if addr == Ipv4Add::new(127, 0, 0, 1) {
        // Here we perform manual conversion 
        // from AddressIsLocalhostError 
        // to Box<dyn Error + 'static> and return error.
        return Err(AddressIsLocalhostError.into());
    }

    // Everyhing is okay, returning addr.
    Ok(Ipv4Addr)
}


fn main() {
    // Let's try to use our function.
    let maybe_address = non_localhost_ipv4_from_file(
        "sure_it_contains_localhost.conf"
    );

    // Let's see what kind of magic Error trait provides!
    match maybe_address {
        // Print address on success.
        Ok(addr) => println!("File was containing address: {}", addr),
        Err(err) => {
            // We sure can just print this error with.
            // println!("{}", err.as_ref());
            // Because Error implementation implies Display implementation.
            // But let's imagine we want to inspect error.

            // Here deref coercion implicitly converts
            // `&Box<dyn Error>` to `&dyn Error`.
            // And downcast_ref tries to convert this &dyn Error
            // back to &IoError, returning either
            // Some(&IoError) or none
            if Some(err) = err.downcast_ref::<IoError>() {
                println!("Unfortunately, IO error occured: {}", err)
            }

            // There's also downcast_mut, which does the same, but gives us
            // mutable reference.
            if Some(mut err) = err.downcast_mut::<AddressParseError>() {
                // Here we can mutate err. But we'll only print it.
                println!(
                    "Unfortunately, what file was cantaining, \
                     was not in fact an ipv4 address: {}",
                    err
                );
            }

            // Finally there's 'is' and 'downcast'.
            // 'is' comapres "erased" type with some concrete type.
            if err.is::<AddressIsLocalhostError>() {
               // 'downcast' tries to convert Box<dyn Error + 'static>
               // to box with value of some concrete type.
               // Here - to Box<AddressIsLocalhostError>.
               let err: Box<AddressIsLocalhostError> = 
                   Error::downcast(err).unwrap();
            }
        }
    };
}
总而言之:我应该说,错误必须向调用者提供有用的信息,除了能够仅仅显示它们之外,所以它们不应该是字符串。错误必须至少实现错误,以便在所有板条箱中保持更不一致的错误处理经验。其余的一切取决于具体情况

Caio alredy提到了铁锈书

但这些链接也可能有用:


我不知道你为什么认为&str作为错误类型是好的设计。你读过吗?另请参见API指南。第三个问题可能过于主观。即使忽略了第二行代码中的语法错误,您想要了解这些东西之间的差异吗?或者只是有人告诉你可以使用Result我想我已经得到了答案。这里没有明确的答案。我已经基本意识到这两种方法之间的权衡。同步错误是故意的伪代码。我不知道你为什么认为&str作为错误类型是好的设计。你读过吗?另请参见API指南。第三个问题可能过于主观。即使忽略了第二行代码中的语法错误,您想要了解这些东西之间的差异吗?或者只是有人告诉你可以使用Result我想我已经得到了答案。这里没有明确的答案。我已经基本意识到这两种方法之间的权衡。同步错误是故意的伪代码。将更新问题以明确这一点