Modifying the inside of Optional.some

I wonder if it's possible to modify some value of Optional without making a copy. The specific case where this comes up is conditionally creating a new value in Dictionary or appending to the current value.

This doesn't work because array gets copied:

switch dict[key] {
    case .none:
        dict[key] = [value]
    case .some(var array):
        array.append(value)
}

This works

if dict[key] == nil {
    dict[key] = [value]
} else {
    dict[key]!.append(value)
}

However the working version has ! in it which reviewers have to spend time on checking if it can fail. One could use ? instead but then it'd silently stop appending if there's a mistake (during refactoring) and would be a nightmare to debug.

In other words, I wonder if it's possible to to directly translate this Rust code:

match map.get_mut(&key) {
    None => { map.insert(key, vec![value]); },
    Some(vec) => vec.push(value),
}

For this particular use case, you can use dictionary's default: subscript to provide the default value:

dict[key, default: []].append(value)
3 Likes

Thanks! Didn't notice this one in the docs.

To answer the broader question: yes, you can do this.

extension Optional {
    @discardableResult
    mutating func modify<ReturnType>(_ closure: (inout Wrapped) throws -> ReturnType) rethrows -> ReturnType? {
        if self == nil { return nil }
        return try closure(&self!)
    }
}

As with some other questions around the use of _modify, the admittedly opaque incantation &self! is carrying a lot of load here. This convinces Swift to elide a copy into a temporary when it passes the inner value into the function argument. I'm honestly not 100% that this works in all edge cases, but so far this has worked in all situations where I've needed to use it.

4 Likes

As to other options with Dictionary specifically, there's also the answer I gave in this thread: Assigning a dictionary without re-hashing - #9 by lukasa