`#[must_use]` for `Option::take`

I wouldn't say this is an "old C API" thing. It's good library design to return something you needed to compute anyway if there's any chance it'd be useful to the caller.

http://elementsofprogramming.com/ talks about this related to rotate APIs, for example. A k-rotation has to calculate where the original element will end up anyway, so might as well return that new locations. Sure, it's n - k in random-access, but you can rotate without random-access, where it becomes particularly helpful to get the iterator.

Vec::push should have returned the &mut T to the inserted element, so you don't need to .last().unwrap() to get it if you needed it. Vec::reserve(n) should arguably return the n-length &mut [MaybeUninit<T>] it just guaranteed existed. Etc.

Rust has lots of -> () things that should return something useful. Sadly it's a breaking change to add them :frowning:

9 Likes

Isn't the whole point of interior mutability to be invisible to the type system (and specifically - the borrow checker)? I don't think we should be allowed to use this information as part of such a rule.

2 Likes

It can also be problematic design; for example, a HashSet<T> must compute <T as Hash>::hash(…) on insert, contains and replace, but if you return that computed value, you've just locked in some implementation details for your HashSet (like the hash function in use, and the state parameter) whether you intended to or not.

As with so much, it becomes a matter of having good taste; if a value is something that must be computed the same way for this functionality to work, regardless of implementation details (like your example of Vec::push returning an &mut to the newly pushed element), then it's good design to return it. But if there's multiple different values that you could compute (my example of Hash::hash for a HashSet), then returning it opens up more problems than it solves.

1 Like

There's the Freeze trait, which was what I was thinking of: Freeze in core::marker - Rust It won't capture something like an &Box<UnsafeCell<..>>, but anything directly containing interior mutability will be gotten.

Swift started out with Rust’s system, i.e., its functions were all need-not-use by default, but then switched to the opposite case, so that functions are all #[must_use] by default, which can be suppressed with @discardableResult. It would be interesting to look at Swift for the rationale for the change and any outcomes now that it's been a few major versions since this change was implemented. (It goes without saying that Swift is a very different language from Rust, but I think there are probably still insights to glean from its history.)

11 Likes