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
There is no such utility, but you can always write a macro:
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);
}
You cannot return from an outer scope from a closure.
I would create an inner function that uses a Result
. This allows you to use the question-mark operator for the various error messages / default values that you'd like to return. You can then call the inner function and take either the success value or error value:
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,
}
}
There's an unstable feature called try blocks that promises to make this slightly less bulky:
#![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,
}
}
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<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 }
}
}
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<String, Error> {
let temp = some_func("pass")
.context("No parameters named pass")?;
Ok(try_decrypt_data(&temp)?)
}