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