-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Cell::update #2171
Comments
There's a more fundamental reason not to do it: cloning Cell contents is unsound. |
That’s because it was the only use allowed until recently. But yeah, |
I think this is small and straightforward enough to open a PR without going through the whole RFC process, with a FCP for libs team review in the PR. |
I'd also like to ask what you think about two more ideas:
These additions would allow the following code to work: let count = Cell::new(5);
count.update(|c| *c += 1);
println!("new count: {}", count.get2());
let v = Cell::new(Vec::new());
v.update(|v| v.push(5));
v.update(|v| v.push(6));
println!("a clone of the vector: {:?}", v.get2()); Playground link with a working example. |
That’s a breaking change, not an extension. (
Neither |
Would there be a way to somehow implement
Yeah... It's a pity that, in order to push something into a let mut v = cell.take();
v.push(foo);
cell.set(v); Seems like there's still a lot of room to improve |
Regarding I agree that the ergonomics of impl<T> Cell<T> {
/// This uses `.get()` then `.set()`
fn get_set<F>(&self, f: F) where T: Copy, F: FnOnce(T) -> T {
self.set(f(self.get()));
}
/// This uses `.take()` to temporarily move out the value
/// (leaving `Default::default()` behind), mutate it, then move it back.
/// That default can be observed if the cell is accessed during the callback.
fn temporary_take<F, R>(&self, f: F) -> R where T: Default, F: FnOnce(&mut T) -> R {
let mut value = self.take();
let result = f(&mut value);
self.set(value);
result
}
} I don’t really like these particular names, but you get the idea. Taking something and then giving it back… It’s tempting to call that "borrowing", but that term obviously already has a strong meaning in Rust that doesn’t apply here. Usage examples: cell.temporary_take(|vec| vec.push(foo)); let vec = cell.temporary_take(|vec| vec.clone()); |
|
Adding to the bikeshed: |
The addition of
While an example for the former was already given by the original author, a motivation for the latter could be retrieving the length of a stored vector. A combination of these is also possible, expressed in the proposed interface of On the other hand, we have to consider zero-cost philosopy and implementation:
Essentially, we have three possibilities of retrieving the input value for the function (
This interface could then be used as follows:
|
In case someone wants to try this out by simple copy&paste, here is an (even slightly more generic) implementation of this proposal: github gist |
Add Cell::update This commit adds a new method `Cell::update`, which applies a function to the value inside the cell. Previously discussed in: rust-lang/rfcs#2171 ### Motivation Updating `Cell`s is currently a bit verbose. Here are several real examples (taken from rustc and crossbeam): ```rust self.print_fuel.set(self.print_fuel.get() + 1); self.diverges.set(self.diverges.get() | Diverges::Always); let guard_count = self.guard_count.get(); self.guard_count.set(guard_count.checked_add(1).unwrap()); if guard_count == 0 { // ... } ``` With the addition of the new method `Cell::update`, this code can be simplified to: ```rust self.print_fuel.update(|x| x + 1); self.diverges.update(|x| x | Diverges::Always); if self.guard_count.update(|x| x.checked_add(1).unwrap()) == 1 { // ... } ``` ### Unresolved questions 1. Should we return the old value instead of the new value (like in `fetch_add` and `fetch_update`)? 2. Should the return type simply be `()`? 3. Naming: `update` vs `modify` vs `mutate` etc. cc @SimonSapin
|
I was wondering why we don't have a method like the following for
Cell
s:This makes some particularly annoying use cases like updating counters easier (see before and after for comparison):
Why require
T: Copy
instead ofT: Default
? Because otherwise you might accidentally run into bugs like:And why not
T: Clone
instead? Because it might introduce hidden costs.For example, to append
"..."
to aCell<String>
, you might do:But if
update
cloned values, that'd introduce a hidden allocation: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:All in all,
T: Default
andT: Clone
have subtle pitfalls, butCell::update
withT: 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 ofCell
anyway, which can be confirmed by a quick ripgrep through therustc
codebase.The text was updated successfully, but these errors were encountered: