Feature Request: Option::update

Currently updating the inner value of an Option<T> require a lot of boilerplace:

let mut value = Some(24);
if let Some(x) = value.as_mut() {
    *x += 10;
}

or

value.as_mut().map(|x| {
   *x += 10;
    x
});

I propose to add an Option::update which allow to mutate the inner value with a function:

impl<T> Option<T> {
    /// Updates the value of the option if is `Some`.
    pub fn update<F>(&mut self, f: F) where F: FnOnce(T) -> T {
        match self.take() {
            Some(x) => {
                let new_value = f(x);
                self.replace(new_value);
            },
            None => {},
        }
    }
}

Example usage:

let value = Some(24);
value.update(|x| x + 10);
7 Likes

This is a good addition, but it would be even better if *value += 10 just worked and did nothing for None.

Since adding the Deref impl would probably cause issues,*value.entry() += 10 could be a middle ground. Now, the issue is what exactly Option::entry would return.

Why not simply use

let mut value = Some(24);
value = value.map(|x| x + 10);

and let the optimizer do its thing?

4 Likes

I don't think that could work because of how those operators work. Furthermore, it isn't clear what should happen for None since there could be two actions. So it isn't very explicit.

That isn't the same, the OP is talking about updating the value within the Option not taking time value out.

FWIW, this doesn't need to return x unless you're chaining it to something further. You can even let += return directly for an unused Option<()>, and this auto-formats nicely:

    value.as_mut().map(|x| *x += 10);
11 Likes

I think it's clear what should happen with None: it stays None. That's because this is operating in Ts and if you have None there is no T to operate with.

The tap crate provides this via an extension trait as TapOption::tap_mut_some.

It's theoretically possible but extremely dumb IMHO and probably would act extremely unexpectedly outside of trivial cases. TL;DR, struct OptionOpAssign<T>(Option<T>), deref to that via pointer cast, impl OpAssign traits for &mut OptionOpAssign<T>. Congrats, you've lifted OpAssign over Option, but you also have a completely meaningless non-mut deref, a bunch of extra types, and have to evaluate the rhs even if the option is None.

2 Likes