17

I'm trying to write a trait that will allow me to "unwrap" multiple nested Option<Option<...<T>>>> to a single Option<T> to better work with an API I am using. I'm trying to create a generic solution, but I can't figure out how to make it work.

This is one of my many attempts:

trait UnwrapOption<T> {
    fn unwrap_opt(self) -> Option<T>;
}

impl<T> UnwrapOption<T> for Option<T> {
    fn unwrap_opt(self) -> Option<T> {
        self
    }
}

impl<T> UnwrapOption<T> for Option<Option<T>> {
    fn unwrap_opt(self) -> Option<T> {
        match self {
            Some(e) => e.unwrap_opt(),
            None => None,
        }
    }
}

fn main() {
    let x = Some(Some(Some(1)));
    println!("{:?}", x.unwrap_opt());
}
error[E0282]: type annotations needed
  --> src/main.rs:22:24
   |
22 |     println!("{:?}", x.unwrap_opt());
   |                      --^^^^^^^^^^--
   |                      | |
   |                      | cannot infer type for type parameter `T` declared on the trait `UnwrapOption`
   |                      this method call resolves to `Option<T>`
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
slipheed
  • 7,170
  • 3
  • 27
  • 38
  • if You know that there's 3 times nesting, why not just simply call `unwrap()` chained 3 times? `fn main() { let x = Some(Some(Some(1))); println!("{:?}", x.unwrap().unwrap().unwrap()); }` – num8er Sep 22 '18 at 03:37
  • 1
    The `Option`s could potentially include None at some point. For example: Some(None) or Some(Some(None)). Both of those should map to None. – slipheed Sep 22 '18 at 03:40
  • I'm new to rust, but understood Your point. Thanks – num8er Sep 22 '18 at 03:41
  • 1
    I would suggest to not name this "unwrap", as this usually means "panic on None" in Rust, rather than the behaviours you are trying to implement here. – mcarton Sep 22 '18 at 09:53

3 Answers3

32

Instead of flattening out the nested option, as the other answer shows, I'd advocate that you never create an Option<Option<T>> that you need to flatten in the first place. In the majority of cases I've seen, it's because someone misuses Option::map when they should have used Option::and_then:

fn main() {
    let input = user_input();

    let a = input.map(add1);
    // a is Option<Option<i32>>

    let b = input.and_then(add1);
    // a is Option<i32>
}

fn user_input() -> Option<i32> {
    Some(10)
}

fn add1(a: i32) -> Option<i32> {
    Some(a + 1)
}

Remember that Rust is a statically typed language; you will always know the exact level of nesting.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 2
    This actually turned out to be my issue - I didn't realize `and_then` was the correct way to map things that return `Option` – slipheed Sep 22 '18 at 16:02
6

I solved it with auto traits (optin_builtin_traits), but I'm not sure if this is the best approach:

#![feature(optin_builtin_traits)]

trait IsOption {}
impl<T> IsOption for Option<T> {}

auto trait IsSingleOption {}
impl<T> !IsSingleOption for Option<Option<T>> {}

trait UnwrapOption {
    type Inner;
    fn unwrap_opt(self) -> Option<Self::Inner>;
}

impl<T> UnwrapOption for Option<T>
where
    Self: IsSingleOption,
{
    type Inner = T;
    fn unwrap_opt(self) -> Option<Self::Inner> {
        self
    }
}

impl<T> UnwrapOption for Option<T>
where
    T: IsOption + UnwrapOption,
{
    type Inner = <T as UnwrapOption>::Inner;
    fn unwrap_opt(self) -> Option<Self::Inner> {
        match self {
            Some(e) => e.unwrap_opt(),
            None => None,
        }
    }
}

fn main() {
    let x = Some(Some(Some(1)));
    println!("{:?}", x.unwrap_opt());
    let x = Some(1);
    println!("{:?}", x.unwrap_opt());
}

playground

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
slipheed
  • 7,170
  • 3
  • 27
  • 38
0

If you have many Options and you want to avoid chaining unwraps, you can use match:

let val = Some(Some(Some(5)));
let res = match val {
    Some(Some(Some(v))) => v,
    _ => 0, // panic or default
};
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366