【问题标题】:Returning default value from function when result is error结果错误时从函数返回默认值
【发布时间】:2018-09-23 13:35:18
【问题描述】:

是否有类似于? 快捷方式的东西,它不是在出现错误时从函数返回结果,而是返回预定义的值?

基本上我想知道是否可以在一行中执行以下操作:

fn index() -> String {
    let temp = some_func("pass"); // some_func returns a Result 
    if temp.is_err() {
        return "No parameters named pass".to_string();
    }
    try_decrypt_data(temp.unwrap())
}

我尝试使用unwrap_or_else(),但这只是返回闭包而不是外部函数。即

try_decrypt_data(params.get("pass").unwrap_or_else(|| return "No parameters named pass".to_string(); )) // This doesn't work

【问题讨论】:

  • 只是为了确定:try_decrypt_data 将解密后的数据返回为String?如果发生错误,您想返回字符串"No parameters named pass",是吗?那么如果解密后的数据正好是"No parameters named pass"呢?您如何判断是否发生了错误,或者这只是解密后的数据?
  • @Lukas Kalbertodt 实际的 try_decrypt_data 返回一个结果,然后我用unwrap_or("...").to_string() 解包,但这与问题无关,所以我把它留在这里以保持简单。跨度>

标签: error-handling rust


【解决方案1】:

这是可能的,但通常不是一个好主意,尤其是在您的示例中(我稍后会解释)。

您不能轻易返回String 并使? 返回默认值,但您可以定义自己的字符串类型并为其实现std::ops::Try。注意Try 仍然不稳定!

让我们看看它是如何工作的:

// Just wrap a string
struct StringlyResult {
    s: String,
}

// Convenience conversion 
impl From<String> for StringlyResult {
    fn from(s: String) -> Self {
        Self { s }
    }
}

// The impl that allows us to use the ? operator
impl std::ops::Try for StringlyResult {
    type Ok = String;
    type Error = String;

    fn into_result(self) -> Result<Self::Ok, Self::Error> {
        if self.s == "No parameters named pass" {
            Err(self.s)
        } else {
            Ok(self.s)
        }
    }

    fn from_error(s: Self::Error) -> Self {
        if s != "No parameters named pass" {
            panic!("wat");
        }
        Self { s }
    }

    fn from_ok(s: Self::Ok) -> Self {
        if s == "No parameters named pass" {
            panic!("wat");
        }
        Self { s } 
    }
}

这样我们就可以像这样实现index()

fn index() -> StringlyResult {
    let temp = some_func("pass")
        .map_err(|_| "No parameters named pass")?; 
    try_decrypt_data(&temp).into()
}

(Complete code on the Playground)

所以是的,Try 特征使用户能够将? 运算符与他们自己的类型一起使用。


但是,如您的示例中所述,这是一个糟糕的想法。您可能已经注意到我上面代码中的“wat”部分。问题是您的 OK 类型已经耗尽了整个类型(该类型的所有实例都是有效的 OK 实例)。

考虑一个函数get_file_size() -&gt; u64。现在这个函数可能会失败(即它无法确定文件大小)。您不能只返回0 来表示发生故障。函数的调用者如何区分函数无法确定文件大小的环境和文件实际为 0 字节大的环境?

同样,您的函数的调用者如何区分发生错误的情况和解密文本字面意思为"No parameters named pass" 的情况?来电者不能!这很糟糕。

请注意,有一些类似的东西,虽然没有那么糟糕,但在 Rust 中仍然不是真正地道:get_file_size() -&gt; i64。在这里,我们可以返回-1 来表示失败。这不那么糟糕,因为-1 永远不会是有效的文件大小! (换句话说,并非您类型的所有实例都是有效的 OK 实例)。但是,在这种情况下,仍然很容易忘记检查错误。这就是为什么在 Rust 中,我们总是希望使用 Result


为了使错误处理更容易,请考虑使用the crate failure。这样,您可以轻松地将字符串用作错误消息,而不会牺牲程序的类型安全性或健全性。示例:

use failure::{Error, ResultExt};

fn index() -> Result<String, Error> {
    let temp = some_func("pass")
        .context("No parameters named pass")?; 
    Ok(try_decrypt_data(&temp)?)
}

【讨论】:

  • 谢谢,这回答了问题,但似乎需要做很多工作来减少 2 行代码:D。虽然在这种情况下,这只是一个个人项目,我知道解密输出会是什么样子,但将来我想我会返回一个结果并有一个包装函数实际上将字符串返回给客户端。
【解决方案2】:

我将创建一个使用Result 的内部函数。这允许您将问号运算符用于您想要返回的各种错误消息/默认值。然后您可以调用内部函数并获取成功值或错误值:

fn index() -> String {
    fn inner() -> Result<String, String> {
        let t1 = some_func("pass").map_err(|_| "No parameters named pass")?;
        let t2 = some_func2(t1).map_err(|_| "A second error")?;
        let t3 = some_func3(t2).map_err(|_| "A third error")?;
        Ok(t3)
    }

    match inner() {
        Ok(v) => v,
        Err(v) => v,
    }
}

有一个名为try blocks 的不稳定功能有望让这个功能稍微小一些:

#![feature(try_blocks)]

fn index() -> String {
    let v = try {
        let t1 = some_func("pass").map_err(|_| "No parameters named pass")?;
        let t2 = some_func2(t1).map_err(|_| "A second error")?;
        some_func3(t2).map_err(|_| "A third error")?
    };

    match v {
        Ok(v) => v,
        Err(v) => v,
    }
}

【讨论】:

    【解决方案3】:

    没有这样的实用程序,但你总是可以写一个宏:

    macro_rules! return_if_err {
        ( $to_test:expr, $default:expr ) => (
            if $to_test.is_err() {
                return $default;
            }
        )
    }
    
    fn pop_or_default(mut v: Vec<i32>) -> i32 {
        let result = v.pop();
        return_if_err!(result.ok_or(()), 123);
    
        result.unwrap()
    }
    
    fn main() {
        assert_eq!(pop_or_default(vec![]), 123);
    }
    

    cannot return from an outer scope from a closure

    【讨论】:

    猜你喜欢
    • 2019-02-22
    • 2018-05-04
    • 2013-01-11
    • 2021-12-29
    • 2013-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多