What about `unwrap_or_from` methods?

I think it would be useful to have Option::unwrap_or_from and Result::unwrap_or_from methods, which call From::from on the fallback value if the value is None/Err.

For example, if you have an Option<String>, you could write

let _: String = option.unwrap_or_from("default value");

which would be equivalent to

let _: String = option.unwrap_or_else(|| String::from("default value"));

Do you think it's justified to add this to the standard library? Also, do you think that unwrap_or_into would be a better name?

1 Like

Could you elaborate on the use case for this, which would make it so common as to need a shorthand?

1 Like

String and Vec are the most common use cases I think. To get some data, I just cloned the rustc repository and searched for .unwrap_or_else(. Ripgrep found 430 usages, and at least 37 of them could be trivially converted to unwrap_or_from (ignoring the ones that could just use unwrap_or). That's fewer than I expected though.

In such a case the type to convert into should always be known to the compiler, so you could write the shorter option.unwrap_or_else(|| "default value".into()) which is a fairer syntactic comparison.

This thread makes me wonder if there’s a strong reason against having

impl<T, F: FnOnce() -> T> From<F> for T {
    fn from(f: F) -> T { f() }
}

in the standard library. Then one could change unwrap_or_else (and lots of other API accepting Fn() -> T parameters) to be more general and wouldn’t need an additional unwrap_or_from. This would unfortunately, probably both would things technically be breaking changes though.

Edit: Damn it, it conflicts with From<T> for T for some reason.

1 Like

Yeah, someone could impl FnOnce for Foo { type Output = Foo; ... }, and then there'd be two competing ways to convert Foo to Foo.

3 Likes

Yeah, I actually know that that's the conflict there, but it's a conflict that realistically should never happen.

Currently Fn-traits are still unstable, so it's actually impossible to get competing impls here, since a closure or fn will never have itself as Output. This means that if we would get some negative-impl feature or something else enabling us to prevent T: FnOnce<(), Output = T> (i.e. T: FnOnce() -> T) before stabilizing Fn-traits, then this conflict might still be possible to resolve.

Edit: A bigger problem might be that we can already do some impl From(fn() -> MyType) for MyType instances currently, so that this whole idea becomes backwards-incompatible.

I mean, technically...

use std::any::Any;

fn foo()->impl Any {
  foo
}

fn main() {
    let bar = None.unwrap_or_else(foo);
}

Although I'm not actually sure how foo::Output would be defined here given the existential type

1 Like

I think the biggest problem with this proposal is that taking it further would imply the need to add unwrap_or_else_xyz for all kinds of common traits with method xyz and each for both Option and Result, and all that just to be able to write my_val.unwrap_or_else_xyz(foo) instead of my_val.unwrap_or_else(||foo.xyz()).

Under this naming scheme unwrap_or_from would be called unwrap_or_else_into.

Hence, you could propose unwrap_or_else_to_owned next, and then unwrap_or_else_to_string and so on.

3 Likes

I don't like heap-allocating string literals when it's avoidable, so I prefer converting the other way. This already works, and if you don't need the owned type, it's more efficient too:

let s: &str = string_option.as_deref().unwrap_or("default value");
3 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.