Returning default value from function when result is error

后端 未结 3 2008
不思量自难忘°
不思量自难忘° 2021-01-23 02:51

Is there something similar to the ? shortcut which instead of returning a result from a function when there is an error, returns a predefined value?

Basical

3条回答
  •  臣服心动
    2021-01-23 03:14

    This is kind of possible, but usually not a good idea, especially not in your example (I will explain that later).

    You cannot easily return a String and make ? return a default value, but you can define your own string type and implement std::ops::Try for it. Note that Try is still unstable!

    Let's see how this would work:

    // Just wrap a string
    struct StringlyResult {
        s: String,
    }
    
    // Convenience conversion 
    impl From 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 {
            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 } 
        }
    }
    

    With that we can implement index() like this:

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

    (Complete code on the Playground)

    So yes, the Try trait enables users to use the ? operator with their own types.


    However, as presented in your example, this is a terrible idea. You probably already noticed the "wat" sections in my code above. The problem is that your OK-type already exhausts the whole type (all instances of the type are valid OK-instances).

    Consider a function get_file_size() -> u64. Now this function can fail (i.e. it cannot determine the file size). You couldn't just return 0 to signal a failure occurred. How would the caller of your function distinguish between an environment in which the function cannot determine the file size and an environment where there the file is actually 0 bytes large?

    Similarly, how would the caller of your function distinguish the situation in which an error occurred and the situation in which the decrypted text is literally "No parameters named pass"? The caller can't! And that's bad.

    Notice that there is something similar, which is not as bad, but still not really idiomatic in Rust: get_file_size() -> i64. Here, we could return -1 to signal a failure. And this is less bad because -1 can never be a valid file size! (in other words, not all instances of your type are valid OK-instances). However, in this case it is still super easy to forget to check for errors. That's why in Rust, we always want to use Result.


    To make error handling easier, consider using the crate failure. With that, you can easily use strings as error messages without sacrificing type safety or sanity of your program. Example:

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

提交回复
热议问题