-
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
Introduce a new type MoveCell<T>
in std::cell
#1659
Conversation
Cc @Amanieu |
I think I like this RFC better considering that |
I would alter Another method, which could be useful, is |
I don't understand what you mean about moving the cell for Edit: s/unsafe/unsound/ |
You should add |
@Amanieu My point is, do that with |
@ticki But on every other type the |
@SimonSapin I recall when talking about this in person you mentioned wanting this before but ended up abandoning the idea in favor of a custom and specialized implementation. Could you elaborate that here as well? (I'm also forgetting the details...) |
The difference there can be simply between |
Because documenting such a double usage type is a pain. |
There is barely anything to document. You make a |
How do you explain in prose to people that sometimes it copies, as it has always done, and that sometimes it doesn't? And that PartialEq and friends work only in the former case? This has been discussed multiple times both IRL and on IRC, let's not bikeshed this again to the infinite. See also the discussion on #1651. |
@alexcrichton I meant this: impl<T> MoveCell<Option<T>> {
#[inline]
pub fn is_none(&self) -> bool {
unsafe {
(*self.0.get()).is_none()
}
}
#[inline]
pub fn take(&self) -> Option<T> {
unsafe {
(*self.0.get()).take()
}
}
}
impl<T> MoveCell<Option<Weak<T>>> {
#[inline]
pub fn upgrade(&self) -> Option<Rc<T>> {
unsafe {
match *self.0.get() {
Some(ref weak) => weak.upgrade(),
None => None,
}
}
}
}
impl<T> MoveCell<Option<Rc<T>>> {
/// Return `Some` if this `Rc` is the only strong reference count,
/// even if there are weak references.
#[inline]
pub fn take_if_unique_strong(&self) -> Option<Rc<T>> {
unsafe {
match *self.0.get() {
None => None,
Some(ref rc) if Rc::strong_count(rc) > 1 => None,
// Not borrowing the `Rc<T>` here
// as we would be invalidating that borrow while it is outstanding:
Some(_) => self.take(),
}
}
}
} Coherence rules would not allow me to write these By the way, the key to these method’s safety is that they’re known not to exercise the cell’s interior mutability (e.g. through reference cycles) while a impl<T> MoveCell<T> where T: WellBehavedClone {
#[inline]
pub fn clone_inner(&self) -> T {
unsafe {
(*self.0.get()).clone()
}
}
}
/**
A Clone impl that will not access the cell again through reference cycles,
which would introduce mutable aliasing.
Incorrect example:
```rust
struct Evil(Box<u32>, Rc<MoveCell<Option<Evil>>>);
impl Clone for Evil {
fn clone(&self) -> Self {
mem::drop(self.1.take()); // Mess with the "other" node, which might be `self`.
Evil(
self.0.clone(), // use after free!
Rc::new(MoveCell::new(None))
)
}
}
unsafe impl WellBehavedClone for Evil {} // Wrong.
let a = Rc::new(MoveCell::new(None));
a.set(Some(Evil(Box::new(5), a.clone()))); // Make a reference cycle.
a.clone_inner();
```
*/
pub unsafe trait WellBehavedClone: Clone {}
unsafe impl<T> WellBehavedClone for Rc<T> {}
unsafe impl<T> WellBehavedClone for Weak<T> {}
unsafe impl<T> WellBehavedClone for Option<T> where T: WellBehavedClone {} In this case |
Thanks for the clarification @SimonSapin! Seems like a good impetus for perhaps stabilizing |
“Copies when the stuff inside the cell is copyable. In case its not, you’re limited to only these methods which move.” I feel like I’m more in favour #1651 (but not very strongly). Interior mutability is quite an advanced topic already and I don’t find |
@rfcbot fcp close I personally feel that this is a bit too niche of a type to fit into the standard library. We already have a somewhat dizzying suite of Most users can get by alright with I'm curious to hear what others think though! |
Team member @alexcrichton has proposed to close this. The next step is review by the rest of the tagged teams: No concerns currently listed. Once these reviewers reach consensus, this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
@alexcrichton Fair enough. What needs to be done to move mitochondria to the nursery? |
(The reason I think the nursery is a good idea is that we can advertise it from the std::cell docs. We can do that even if it isn't in the nursery, but we want the docs to link to long-term maintained "semi-official" solutions, and that's sort of what the nursery does) |
I would personally feel like it's a bit premature to move to the nursery, basically for the same reasons as before where this doesn't seem to be a widespread issue in the sense that we need to raise awareness and bless a crate (some of the purposes of the nursery) |
Are we okay with linking to this in the docs without it being in the nursery? |
I agree with @alexcrichton: for the vast majority of users, the overhead of Can you explain why you think it's important to raise global awareness of this library? |
I've seen this crop up multiple times, that's all -- it feels like it fills a hole in |
Can you elaborate on what, specifically, is coming up? In particular, why isn't |
It's basically:
|
I'm pretty skeptical of the overhead issue (except for very rare cases like Servo where The FFI point is more interesting -- it wasn't mentioned in the RFC's motivation. Can you give a more detailed example or two showing how/why this gets used there? |
Is there something about the design of Servo that makes you expect that no other crate would do similar things? Or is there another reason you think that Servo is a rare case? |
In Servo we’d use MoveCell instead of RefCell in cases where each node of something contains a few cells, and there can be many nodes so saving one word of memory per cell ends up being significant. I don’t think Servo is that exceptional in this respect, there’s probably other applications that handle lots of data and are memory-constrained. But I also think it’s fine for MoveCell not to be in the standard library. If you get to the point where MoveCell can bring significant memory savings to your app, you can afford to add one dependency line to your Cargo.toml file. |
I personally feel that Servo's use case quite well motivates the existence of That being said, being a dependency of Servo does not imply that something should be in the standard library. As @SimonSapin mentioned once you can really see the wins of this optimization adding one dependency is very small. |
The FFI point is this: When talking with C, you often need to turn off noalias semantics because C might mutate things. Some values (especially C-managed ones) might need C-like aliasing semantics (mutation+sharing allowed). If they have subfields which you wish to deal with from the Rust side, you need to make at least those fields mutable via interior mutability. |
🔔 This is now entering its final comment period, as per the review above. 🔔 psst @alexcrichton, I wasn't able to add the |
Doesn't |
@jbj right yeah that's the advantage of using |
I think it's worth having in the standard library because it's fairly common to require inner mutability of elements in a large array, where |
My feeling is the same as @aturon's. Which is to say that yes |
The FCP has elapsed. At this point, the libs team consensus continues to be that while this functionality is worthwhile in general, it doesn't yet merit adding an additional That said, the related RFC #1651 would also address the motivations of this RFC, but do so without adding an entirely new type. There's not yet a clear consensus on that RFC, but continued discussion should probably happen there. Thanks all! |
No description provided.