Description
Proposal
Add Option::try_or_else
.
(note: This was formerly proposed as Option::result_or_else
that only handled Result
)
Problem statement
This generalizes Option::or_else
to also handle Result
, which is very useful in cases where we want to convert an Option<T>
to a T
, but use a fallible (Result<T>
) fallback path in a combinator chain.
Motivation, use-cases
// Our program needs a string for something (e.g. filename, database table name, etc).
// To start, we can get this as an optional string - here a CLI argument, but it could be anything; e.g.
// a value parsed from JSON, etc. The `.nth(1)` call here gives us an `Option<String>`.
// We want to compute a fallback value, but doing so can fail (e.g. return `Result<String>`).
// Here's the trick - we use `.map(Ok)` to convert our `Option<String>` to `Option<Result<String>>`,
// which means the type signature works for `.unwrap_or_else()`, giving us in the end a Result<String>,
// on which we can use the regular `?` operator.
let v: String = std::env::args()
.nth(1)
.map(Ok)
.unwrap_or_else(|| std::fs::read_to_string("/etc/someconfig.conf"))?
;
This could instead be:
let v: String = std::env::args()
.nth(1)
.try_or_else(|| std::fs::read_to_string("/etc/someconfig.conf"))?;
This convenience method is particularly useful when chaining further combinators, e.g.:
let v: String = std::env::args()
.nth(1)
.try_or_else(|| std::fs::read_to_string("/etc/someconfig.conf"))
.map(|v| v.trim().to_string())?;
Solution sketches
index 28ea45ed235..05b48728ae3 100644
--- a/library/core/src/option.rs
+++ b/library/core/src/option.rs
@@ -1346,7 +1346,8 @@ pub const fn or(self, optb: Option<T>) -> Option<T>
}
/// Returns the option if it contains a value, otherwise calls `f` and
- /// returns the result.
+ /// returns the result. See also [`try_or_else`] which can also handle
+ /// [`Result`].
///
/// # Examples
///
@@ -1372,6 +1373,35 @@ pub const fn or_else<F>(self, f: F) -> Option<T>
}
}
+ /// Returns the option if it contains a value, otherwise calls `f` and
+ /// returns the result. This is a more general version of [`or_else`]
+ /// that can also handle [`Result`].
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// fn nobody() -> Option<&'static str> { None }
+ /// fn vikings() -> Option<&'static str> { Some("vikings") }
+ /// fn success() -> Result<&'static str> { Ok("vikings") }
+ /// fn barbarians() -> Result<&'static str> { Err(io::Error::new(io::ErrorKind::Other, "oh no!")) }
+ ///
+ /// assert_eq!(Some("barbarians").try_or_else(vikings), Some("barbarians"));
+ /// assert_eq!(None.try_or_else(vikings), Some("vikings"));
+ /// assert_eq!(None.try_or_else(nobody), None);
+ /// assert_eq!(None.try_or_else(success).unwrap(), "vikings");
+ /// assert_eq!(None.try_or_else(barbarians).is_err());
+ /// ```
+ #[inline]
+ #[unstable(feature = "try_or_else", issue = "1")]
+ #[rustc_const_unstable(feature = "try_or_else", issue = "1")]
+ pub const fn try_or_else<R: ops::Try<Output = T>, F: FnOnce() -> R>(self, f: F) -> R {
+ if let Some(v) = self {
+ ops::Try::from_output(v)
+ } else {
+ f()
+ }
+ }
+
/// Returns [`Some`] if exactly one of `self`, `optb` is [`Some`], otherwise returns [`None`].
///
/// # Examples
Links and related work
I discovered/made up this idiom of using map(Ok).unwrap_or_else()
while working on a project, and it made some code much more elegant versus what I was doing before with multiple-line match
statements.
I then did some looking around via code search engines:
- https://cs.github.com/?scopeName=All+repos&scope=&q=map%28Ok%29.unwrap_or_else
- https://sourcegraph.com/search?q=context:global+map%28Ok%29.unwrap_or_else&patternType=literal
And there's quite a lot of usage of this. Enough, I think that there's an argument for promoting this pattern to std.
I was chatting with @cuviper about this and he mentioned:
"another way to write this is option.ok_or(()).or_else(|()| new_result)?
"
I did some further searching, and there's also some code using this variant, though less:
- https://cs.github.com/?scopeName=All+repos&scope=&q=.ok_or%28%28%29%29.or_else%28
- https://sourcegraph.com/search?q=context:global+.ok_or%28%28%29%29.or_else%28&patternType=literal
Notably the first link shows a hit in cargo.
Later, several people pointed out in discussion that by using the Try
trait, we can effectively generalize the current Option::or_else
to handle both Option
and Result
for the callback.
What happens now?
This issue is part of the libs-api team API change proposal process. Once this issue is filed the libs-api team will review open proposals in its weekly meeting. You should receive feedback within a week or two.