-
Notifications
You must be signed in to change notification settings - Fork 1.6k

Description
I was wondering why we don't have a method like the following for Cell
s:
impl<T: Copy> Cell<T> {
fn update<F: FnMut(T) -> T>(&self, f: F) {
self.set(f(self.get()));
}
}
This makes some particularly annoying use cases like updating counters easier (see before and after for comparison):
data.dep_kind.set(cmp::max(data.dep_kind.get(), dep_kind));
data.dep_kind.update(|k| cmp::max(k, dep_kind));
self.has_errors.set(self.has_errors.get() | old_has_errors);
self.has_errors.update(|e| e | old_has_errors);
self.count_imm.set(self.count_imm.get() + 1);
self.count_imm.update(|c| c + 1);
Why require T: Copy
instead of T: Default
? Because otherwise you might accidentally run into bugs like:
foo.update(|f| {
let x = bar.set(foo.get()); // Ooops, `foo.get()` returns `0` instead of `f`!
f + x
});
And why not T: Clone
instead? Because it might introduce hidden costs.
For example, to append "..."
to a Cell<String>
, you might do:
let mut s = foo.take();
s.push_str("...");
foo.set(s);
But if update
cloned values, that'd introduce a hidden allocation:
let mut s = foo.update(|mut s| {
s.push_str("...");
s
});
Here's a nicer version of the same thing. It still has an allocation, but with T: Default
there wouldn't be one, so one might (incorrectly) expect this to be efficient:
foo.update(|mut s| s + "foo");
All in all, T: Default
and T: Clone
have subtle pitfalls, but Cell::update
with T: Copy
seems like a safe choice and would be a nice addition to the standard library. After all, T: Copy
is by far the most common use of Cell
anyway, which can be confirmed by a quick ripgrep through the rustc
codebase.