-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Tracking issue for Cell::update #50186
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
Comments
Not a blocking issue for merging this as an unstable feature, so I'm registering my concern here: The
Maybe there are other options? Data point: the |
I like the second option (taking a let c = Cell::new(Vec::new());
c.update(|v| v.push("foo")); Perhaps we could have two methods with different names that clearly reflect how they internally work? fn get_update<F>(&self, f: F) where T: Copy, F: FnMut(&mut T);
fn take_update<F>(&self, f: F) where T: Default, F: FnMut(&mut T); |
What do you think, @SimonSapin? Would two such methods, |
The It looks like there is a number of slightly different possible APIs for this. I don’t have a strong opinion on which is (or are) preferable. |
Implement rfc 1789: Conversions from `&mut T` to `&Cell<T>` I'm surprised that RFC 1789 has not been implemented for several months. Tracking issue: #43038 Please note: when I was writing tests for `&Cell<[i32]>`, I found it is not easy to get the length of the contained slice. So I designed a `get_with` method which might be useful for similar cases. This method is not designed in the RFC, and it certainly needs to be reviewed by core team. I think it has some connections with `Cell::update` #50186 , which is also in design phase.
Taking Unfortunately it seems that it would have to be
I hope, I'm wrong. |
@CodeSandwich it is a fundamental restriction of We could make an API that takes a closure that takes |
I think, that a modifier method might be safe if we just forbid usage of reference to I've created a simple implementation of such method and I couldn't find any hole in it. It's a limited solution, but it's enough for many use cases including simple number and collections modifications. |
A |
That's absolutely right :( |
It seems like, the semantic are a bit different between What shall we name those variants? Also there is another variation: pub unsafe fn update_unsafe<F>(&self, f: F) -> T where F: FnOnce(T) -> T; |
if you’re gonna use |
IMO, closure receiving a &mut to a stack copy isn't that advantageous ergonomically in terms of offering flexibility. You still need to jump through hoops to get the old value if you want to. Consider: let id = next_id.update(|x| { let y = *x; *x += 1; y }); I think the most universally applicable thing to do would be to offer all of things.update(|v| v.push(4)); // returns ()
// Alternative with v by-move. I'd guess this one isn't as useful, and you can easily use
// mem::swap or mem::replace in the by-ref version instead if you need it.
things.update(|v| { v.push(4); v });
let id = next_id.get_and_update(|x| x + 1);
let now = current_time.update_and_get(|x| x + 50); These are 3 additional names, but I think they'll make the function more useful. A by-mut-ref update would only be useful for complex types where you're likely to do in-place updates, but I find myself doing the .get() -> update/keep value -> .set() pattern a lot with |
Related conversation on a similar PR (for |
Has it been considered to use an arbitrary return type on top of updating the value? That is (naming aside), fn update1<F>(&self, f: F) where F: FnOnce(T) -> T;
fn update2<F, R>(&self, f: F) -> R where F: FnOnce(T) -> (T, R); The second method has a very functional feel to it and appears both flexible and sound (i.e. you can't return &T). You could get either EDIT: Something to chew on: fn dup<T: Copy>(x: T) -> (T, T) { (x, x) }
let i = i.update2(|i| (i + 1, i)); // i++
let i = i.update2(|i| dup(i + 1)); // ++i |
+1 for a Finally, I think |
It sounds like accepting I'm sort of combining previous ideas here. How about this? fn update<F>(&self, f: F) where T: Copy, F: FnOnce(T) -> T;
fn update_map<F, U>(&self, f: F) -> U where T: Copy, F: FnOnce(T) -> (T, U);
fn take_update<F>(&self, f: F) where T: Default, F: FnOnce(T) -> T;
fn take_update_map<F, U>(&self, f: F) -> U where T: Default, F: FnOnce(T) -> (T, U); |
A completely different option could be to not take a function as argument, but return a 'smart pointer' object that contains the taken/copied value and writes it back to the cell when it is destructed. Then you could write a.take_update().push(1);
*b.copy_update() += 1; |
@m-ou-se I think that would be let mut cu1 = b.copy_update();
let mut cu2 = b.copy_update();
*cu1 += 1;
*cu2 += 1;
*cu1 += *cu2; |
The proposed implementation appears to move the value out of the cell and into the smart pointer; pointer in this case is a misnomer. This will lead to the old c++ auto_ptr problem: not unsound, but surprising in nontrivial use cases (ie, if two exist at the same time, one will receive a garbage value) |
None of the proposed solutions (with a All of them have this 'update while updating' problem. That's not inherent to a specific solution, but inherent to With the current (unstable) let a = Cell::new(0);
a.update(|x| {
a.update(|x| x + 1);
x + 1
});
assert_eq!(a.get(), 1); // not 2 With a let a = Cell::new(0);
a.update(|x| {
a.update(|x| *x += 1);
*x += 1;
});
assert_eq!(a.get(), 1); // not 2 With a copy-containing 'smart pointer': let a = Cell::new(0);
{
let mut x = a.copy_update();
*a.copy_update() += 1;
*x += 1;
}
assert_eq!(a.get(), 1); // not 2 There is no way to avoid this problem. Every possible solution we come up with will have this problem. All we can do is try to make it easier/less verbose to write the commonly used .get() + .set() (or .take() + .set()) combination. |
That's not what I said though. A better summary of what I said is "the name is different because the usage pattern of the routine is different." That's not circular reasoning. Like, we don't have a rule in std that every single method that takes a closure has to end with the suffix The main issue here, from what I can tell, is the similarity between
Sure... If we don't add |
If
|
Daniel Henry-Mantilla wrote:
That way a future release could always decide to return a copy of T rather
than ().
I believe this would still not be backwards compatible if `T` is
`#[must_use]`.
Peter
…On Fri, 28 Jun 2024 at 16:03, Daniel Henry-Mantilla < ***@***.***> wrote:
If Cell::update() returns the old value
-
it is consistent with RefCell::replace_with();
-
but just to allow replacing:
let old = cell.get();
cell.set(…);if old … {
with:
let old = cell.update(|old| …);if old … {
which isn't that much more useful than plain set-&-get.
If Cell::update() returns the new value:
-
It allows replacing:
let old = cell.get(); // <- or inlined.let new = …;
cell.set(new);if new … {
with:
let new = cell.update(|old| …);if new … {
which, in the non-inlined cell.get() case, starts having maybe enough
value.
-
But it is inconsistent with RefCell::update_with()!
I kind of agree that, whilst not a deal breaker thanks to the
different name, it's kind of unfortunate to have this inconsistency-ish
thing, just for the sake of a tiny convenience around returning the new
value. It is probably not worth doing.
If Cell::update() does not return anything
Then we lose *some* convenience, but we retain the main counter.update(|n|
n + 1) aspect of the API.
------------------------------
Conclusion
Since the "return-old" is barely more convenient than explicit .set() and
.get(), and "return-new" is slightly inconsistent with other "similar"
APIs, returning nothing may be the most sensible for the time being.
------------------------------
Aside: a possibility to punt on the decision Click to see
would be to have the API of Cell::update() return (), but with the
specific choice of () being opaque:
impl<T: Copy> Cell<T> {
fn update<'t>(&self, f: impl FnOnce(T) -> T) -> impl 't + Sized
where
T: 't,
{
self.set(f(self.get()));
}}
That way a future release could always decide to return a copy of T
rather than ().
—
Reply to this email directly, view it on GitHub
<#50186 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABE2WY2MW4CFM5UW7Z3QELZJV3NPAVCNFSM6AAAAABI4TSL7WVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOJXGEZTOOBZHE>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Sorry, what I mean to say is "...assuming that there needs to be a method named 'update'". I think the baseline here is that we want a closure-taking method. The name and the return value are the "negotiables". |
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
I also think having such method only for copy types makes sense. For the impl<T: Default> Cell<T> {
/// Takes the contained value to call a function
/// on a reference and then moves it back in the cell.
///
/// The cell will contain `Default::default()`
/// in the function scope or if it panics.
///
/// # Examples
///
/// ```
/// use std::cell::Cell;
/// use std::sync::mpsc::channel;
///
/// let (tx, rx) = channel();
///
/// let c = Cell::new(Some(tx));
///
/// c.take_inspect(|t| t.as_ref().unwrap().send(true).unwrap());
/// c.take_inspect(|t| t.as_ref().unwrap().send(c.take().is_some()).unwrap());
///
/// println!("Received from channel: {}", rx.recv().unwrap());
/// assert_eq!(rx.recv().unwrap(), false);
/// assert_eq!(c.take().is_some(), true);
/// ```
#[inline]
pub fn take_inspect<F>(&self, f: F)
where
F: FnOnce(&T),
{
let moved = self.take();
f(&moved);
self.set(moved);
}
} Although less useful I still find nice to have a one line method to do something on a reference to the value inside a cell (which Is there a RFC proposing this idea already ? |
FCP complete at [1]. Link: rust-lang#50186 (comment) [1]
FCP complete at [1]. Link: rust-lang#50186 (comment) [1] Closes: rust-lang#50186
FCP complete at [1]. Link: rust-lang#50186 (comment) [1] Closes: rust-lang#50186
Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
libs-api discussed this again and decided that |
I can't update the top post because it was created by a deleted user, but the API will be: impl<T: Copy> Cell<T> {
pub fn update(&self, f: impl FnOnce(T) -> T);
} |
…pratt Apply requested API changes to `cell_update` Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
…pratt Apply requested API changes to `cell_update` Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
Rollup merge of rust-lang#139273 - tgross35:cell-update-changes, r=jhpratt Apply requested API changes to `cell_update` Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
Crosslinking, FCP for the changed API is currently ongoing at #134446 (comment) |
Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
…pratt Apply requested API changes to `cell_update` Do the following: * Switch to `impl FnOnce` rather than a generic `F`. * Change `update` to return nothing. This was discussed at a libs-api meeting [1]. Tracking issue: rust-lang#50186 [1]: rust-lang#134446 (comment)
…jhpratt Stabilize the `cell_update` feature Included API: ```rust impl<T: Copy> Cell<T> { pub fn update(&self, f: impl FnOnce(T) -> T); } ``` FCP completed once at rust-lang#50186 (comment) but the signature has since changed. Closes: rust-lang#50186
Rollup merge of rust-lang#134446 - tgross35:stabilize-cell_update, r=jhpratt Stabilize the `cell_update` feature Included API: ```rust impl<T: Copy> Cell<T> { pub fn update(&self, f: impl FnOnce(T) -> T); } ``` FCP completed once at rust-lang#50186 (comment) but the signature has since changed. Closes: rust-lang#50186
Included API: impl<T: Copy> Cell<T> { pub fn update(&self, f: impl FnOnce(T) -> T); } Closes: rust-lang/rust#50186
Stabilize the `cell_update` feature Included API: ```rust impl<T: Copy> Cell<T> { pub fn update(&self, f: impl FnOnce(T) -> T); } ``` FCP completed once at rust-lang/rust#50186 (comment) but the signature has since changed. Closes: rust-lang/rust#50186
This issue tracks the
cell_update
feature.The feature adds
Cell::update
, which allows one to more easily modify the inner value.For example, instead of
c.set(c.get() + 1)
now you can just doc.update(|x| x + 1)
:The text was updated successfully, but these errors were encountered: