当结果出错时,从函数返回默认值

时间:2018-04-13 08:45:18

标签: error-handling rust

是否存在与?快捷方式类似的内容,而不是在发生错误时从函数返回结果,返回预定义的值?

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

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

3 个答案:

答案 0 :(得分:4)

有点可能,但通常不是一个好主意,特别是在你的例子中(我稍后会解释)。

您无法轻松返回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特征使用户能够将?运算符与他们自己的类型一起使用。

但是,如您的示例中所示,这是一个糟糕的主意。您可能已经注意到&#34; wat &#34;上面代码中的部分。问题是你的OK类型已经耗尽了整个类型(该类型的所有实例都是有效的OK实例)。

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

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

请注意,有类似的东西,但并不是那么糟糕,但在Rust中仍然不是真正的惯用语:get_file_size() -> 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)?)
}

答案 1 :(得分:1)

没有这样的实用程序,但您始终可以编写宏,例如:

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

您无法从闭包中返回外部作用域。

答案 2 :(得分:1)

我将创建一个使用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,
    }
}