12

It would be nice if Rust's Option provided some additional convenience methods like Option#flatten and Option#flat_map, where flatten would reduce an <Option<Option<T>> to Option<T>, and flat_map would work like map, but takes a method/closure that returns an Option and flattens it.

flat_map is pretty straightforward:

fn opt_flat_map< T, U, F: FnOnce(T) -> Option<U> >(opt: Option<T>, f: F) -> Option<U> {
  match opt {
    Some(x) => f(x),
    None => None
  }
}

flatten is more complex, and I don't really know how to go about defining it. It might look something like:

fn opt_flatten<T, U>(opt: Option<T>) -> Option<U> {
  match opt {
      Some( Some(x) ) => flatten_option( Some(x) ),
      _ => opt
  }
}

But that certainly doesn't work. Any thoughts?

Also, how would I go about implementing these methods on the Option enum, so that I can use them natively on an Option instance? I know I need to add the type signature in somewhere around impl OptionExts for Option<T>, but I'm at a loss...

Hope this makes sense and I apologize for my imprecise terminology--I'm brand new to Rust.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Jacob Brown
  • 7,221
  • 4
  • 30
  • 50

2 Answers2

22

These probably already exist, just as different names to what you expect. Check the docs for Option.

You'll see flat_map more normally as and_then:

let x = Some(1);
let y = x.and_then(|v| Some(v + 1));

The bigger way of doing what you want is to declare a trait with the methods you want, then implement it for Option:

trait MyThings {
    fn more_optional(self) -> Option<Self>;
}

impl<T> MyThings for Option<T> {
    fn more_optional(self) -> Option<Option<T>> {
        Some(self)
    }
}

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

For flatten, I'd probably write:

fn flatten<T>(opt: Option<Option<T>>) -> Option<T> {
    match opt {
        None => None,
        Some(v) => v,
    }
}

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

But if you wanted a trait:

trait MyThings<T> {
    fn flatten(self) -> Option<T>;
}

impl<T> MyThings<T> for Option<Option<T>> {
    fn flatten(self) -> Option<T> {
        match self {
            None => None,
            Some(v) => v,
        }
    }
}

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

Would there be a way to allow flatten to arbitrary depth

See How do I unwrap an arbitrary number of nested Option types?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • awesome, thanks so much for the examples and the pointer to `and_then`, which is exactly what I wanted as `flat_map`. Would there be a way to allow `flatten` to arbitrary depth, e.g., for `Some(Some(Some(Some(3))))`, or would we have to implement traits for each level? – Jacob Brown Feb 17 '15 at 17:12
  • @kardeiz that's an interesting question. To my knowledge, there's no way of dealing with arbitrary nesting of types. I'd suggest opening a new question, as I'd like to see the answer myself. ^_^ – Shepmaster Feb 17 '15 at 18:40
  • @Shepmaster I'm pretty sure that this would require [negative bounds](https://github.com/rust-lang/rfcs/pull/586) to implement, since you would need a trait that is implemented for all types, but treated specially for `Option`s. – Dylan Feb 17 '15 at 23:33
  • I also had trouble finding flatmap, that's why there's a pull request for including it in the documentation of `and_then`: https://github.com/rust-lang/rust/pull/22111 – robinst Feb 18 '15 at 07:47
6
fn flatten<T>(x: Option<Option<T>>) -> Option<T> {
    x.unwrap_or(None)
}

In my case, I was dealing with an Option-returning method in unwrap_or_else and forgot about plain or_else method.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Demur Rumed
  • 421
  • 6
  • 17