Repository navigation
Open
Labels
C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCI-lang-radarItems that are on lang's radar and will need eventual work or consideration.Items that are on lang's radar and will need eventual work or consideration.S-tracking-unimplementedStatus: The feature has not been implemented.Status: The feature has not been implemented.T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.
Description
Feature gate: #![feature(cell_get_cloned)]
This is a tracking issue for Cell::get_cloned for T: CloneFromCopy (e.g. Rc<T>, Arc<T>, etc.).
Public API
/// Safety: can call `Self::clone(&*ManuallyDrop::new(ptr::read(ptr_to_self)))`
#[marker]
pub unsafe trait CloneFromCell: Clone {}
// Can't do this, unsound
// unsafe impl<T: Copy> CloneFromCell for T {}
unsafe impl<T: ?Sized> CloneFromCell for Rc<T> {}
unsafe impl<T: ?Sized> CloneFromCell for Arc<T> {}
unsafe impl<T: ?Sized> CloneFromCell for rc::Weak<T> {}
unsafe impl<T: ?Sized> CloneFromCell for sync::Weak<T> {}
// basically all std types that don't have indirection:
unsafe impl<T: CloneFromCell, const N: usize> CloneFromCell for [T; N] {}
unsafe impl<T: CloneFromCell> CloneFromCell for Option<T> {}
unsafe impl<T: CloneFromCell, E: CloneFromCell> CloneFromCell for Result<T, E> {}
// ...
impl<T: CloneFromCell> Cell<T> {
pub fn get_cloned(&self) -> Self;
}Steps / History
(Remember to update the S-tracking-* label when checking boxes.)
- Implementation: add CloneFromCell and Cell::get_cloned #145685
- Final comment period (FCP)1
- Stabilization PR
Unresolved Questions
- Should this remain
get_clonedor move to theCloneimpl forCell? AddCell::getforRc-like shallow-copying types libs-team#572 (comment) - Concern: the soundness conditions here are extremely tricky to get right. We need to have a warning about this.
- The soundness requirement on
CloneFromCopyas stated above is likely wrong.
cc @rust-lang/libs-api @rust-lang/lang
Footnotes
Metadata
Metadata
Assignees
Labels
C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCCategory: An issue tracking the progress of sth. like the implementation of an RFCI-lang-radarItems that are on lang's radar and will need eventual work or consideration.Items that are on lang's radar and will need eventual work or consideration.S-tracking-unimplementedStatus: The feature has not been implemented.Status: The feature has not been implemented.T-libsRelevant to the library team, which will review and decide on the PR/issue.Relevant to the library team, which will review and decide on the PR/issue.
Activity
theemathas commentedon Aug 13, 2025
frank-king commentedon Aug 13, 2025
Tis copied (viaptr::read) before callingT::clone, and thus there's no reference to the copiedT, soT::clonecannot do harm to it.ais523 commentedon Sep 12, 2025
I believe that, given the stated safety condition, this is unsound.
Box<T> where T: Cloneis an example of a type which can, according to the stated safety condition, implementCloneFromCopy(assume either that the special case that makes the copy itself UB gets removed, or that you are using aMyBox<T>that doesn't have such a case). The reason is that a copy of aBoxis just a pointer to the contents of the box (like the original is), and you can clone via the copy of the pointer just as easily as you can clone via the original pointer.However, cloning a
Box<T>can change the value of aCell<Box<T>>(ifThas a sufficiently perversecloneimplementation). Doing that won't cause the copy of the pointer to be dropped – but it could cause the allocation that it points to to be dropped (e.g. theCloneimplementation could take theBoxfrom theCellby swapping anotherBoxinto its place, then drop theBoxit took, causing the copy of theBoxto now be pointing to deallocated memory and subsequently causing a use-after-free).The correct safety requirement is unrelated to the ability to clone from a copy: rather, the requirement we want is that cloning does not write to any cells in a way that could cause the value inside a
Cell<T>to be modified, moved out of the cell, or dropped. Cloning from a copy (rather than the original) doesn't actually solve any of these problems: modifying the value could drop something that the copy of the value references, as could dropping it, and moving it out of the cell makes it possible to modify or drop it without the cell being involved. However, once you enforce that more stringent safety requirement, there's no longer a need to actually do the copy out of the cell any more! SoCloneFromCopyis probably the wrong trait to be using here – it should be some sort ofCloneFromCellthat asserts that the cloning operation doesn't change cells in a problematic way.(Some context: I've recently been doing a study into what Rust would look like if "create a new value by copying the bits of an existing value, even if the type isn't
Copy" were supported by the language and type system as a first-class operation. One of the main conclusions was that to be usable, the copies generally have to have a lifetime – otherwise, you can't guarantee that things that the original value referenced will still be alive by the time you try to use the copy, and thus you can't soundly use them via the copy. That made me realise that the API written here doesn't work, because it doesn't specify a lifetime for how long the copy remains valid, and if you could guarantee that the copy were still valid you could just as easily guarantee that theCellstill had contents that could safely be used directly. Note that without such a lifetime, evenRccan't be cloned safely from a copy: the originalRcthat you copied might have been dropped since, and it might have been the last reference.)frank-king commentedon Sep 13, 2025
I strongly agree with you. As from the ACPs #572 and #648, we started from the same point that we want
Cell<Rc<T>>be assessed byclone(or byget), and besidesRc<T>, I've just thinking of more general cases, so I proposed a traitCellGet(now theCloneFromCopy) that may apply to types similar toRc<T>. The current statement of the safety property is not accurate enough and I really appreciate your feedback.Is your statement an informal sketch or do you already have done some verification or proof?
What an interesting conclusion. It sounds just like "borrowing" with auto-deref?
ais523 commentedon Sep 13, 2025
It's based on the "cell permission" model, in which each cell is conceptually tied to a "permission object" (you can tie multiple cells to the same object but don't have to), and you can't read the cell unless you could safely shared-borrow its permission object, and can't write the cell unless you could safely exclusive-borrow its permission object. I haven't personally proven the model itself to be correct, but there is related work, which does contain proofs, e.g.
ghost-cellis effectively an implementation of the cell permission model and the theory behind it has been formally proven (although I'm not sure whether or not there's a proof that the implementation actually matches the theory).The basic idea is that in order to read inside a cell, you have to shared-borrow its permission object, so you can't then do things that require an exclusive reference (write/move) to that same cell as that would require a conflicting borrow. It's difficult to express "the same cell" in the type system, but you can broaden it to "cells of the same type" and get a safety argument that way. I'd consider it more of an informal argument than anything – a formal model exists, but it hasn't been formally proven to be relevant in this context, and the way the model is applied to the situation hasn't been formally proven to be correct either.
Thinking about what I just wrote, we might not need to explicitly ban drops; you can't drop the cell you're cloning from, because
get_clonedtakes a shared reference to the cell which prevents it being dropped, and if the cell isn't dropped you can't drop its contents without first overwriting them or moving them out (thus, banning moves and writes is enough).It's definitely related – the primary difference is that the thing that is being borrowed doesn't necessarily need to exist in memory in a usable form at all (e.g. it might be in a register, or might be a field of a
#[repr(packed)]struct and thus unreferenceable due to being misaligned). It's pretty similar to a shared reference, but based on values rather than addresses (thus there isn't a pointer behind it, like there would be for a reference, meaning that you can't implement theDereftrait: all you have is a bit pattern).tgross35 commentedon Sep 16, 2025
Seeing some iterations of the implementation PR, I think
CloneFromCopymay not be the best name because it seems to indicate some relation toCopy. However, (1) it doesn't apply to everythingCopy(which of course impliesClone), and (2) it applies to many things that have nothing to do withCopy.Unhelpfully I don't have any great suggestions.
PartialPureClonecrossed my mind becauseClonemust be free of side effects for the local type (i.e. theRcstruct itself) but it may have limited side effects via indirection (i.e. theRccounters). OrFreezeClonefor similar reasons.programmerjake commentedon Sep 16, 2025
I think
CloneFromCellis a pretty good name because it literally describes the operation we're trying to do, also because if you have a&Cell<T>you can also obtain a&Cell<Field>for each field ofT(edit: stuff in parenthesis turns out to not work: #145329 (comment)this includes stuff like).Derefbut for like going from&Cell<Box<U>>to&Cell<U>or&Cell<&mut U>to&Cell<U>programmerjake commentedon Sep 16, 2025
ais523 commentedon Sep 16, 2025
programmerjake commentedon Sep 16, 2025
I thought it composes better than you might expect, because, assuming there's a safe way to go from&Cell<Box<T>>to&Cell<T>(Box<T>: DerefCellfrom above),T: CloneFromCellcan't go messing with anyCell<Box<T>>that might be the passed inCell. Same kind of thing for any otherU: CloneFromCellthat might contain aTand callT::clone_from_cell.Though now that I think about it more,
&Cell<Box<T>>to&Cell<T>is not sound since you can obtain both shared references at once and then swap out and drop theBox<T>while the&Cell<T>is still pointing to it. So, yeah,CloneFromCellshould be anunsafetrait andclone_from_cellwould have to guarantee it doesn't modify anything that could possibly containSelfeither directly or indirectly.tgross35 commentedon Sep 24, 2025
Another concern brought up in the implementation PR: since this is not sound to implement for the general
T: Copybut is sound for mostCopyitems, that means supporting this for all possible types means a manual impl for (almost) everyCopyitem in std and elsewhere. If API shape can be adjusted to allow a blanketT: Copyimpl, that seems advantageous. If that is not possible, it might be worth reframing this as specifically for supporting types that aren'tCopyrather than a general accessor (givenCopytypes can just.get()).The exact possibilities here will depend on whether or not the implementation here moves to the
Cloneimpls rust-lang/libs-team#572 (comment).32 remaining items
rkjnsn commentedon Feb 4, 2026
It was mentioned briefly above, but it would be very useful if
CloneFromCellcould be derivable. E.g., I currently have a struct containing aString, anRc, an enum, and someCopytypes, and it would be great if I could just slap#[derive(CloneFromCell)]on it and the enum to be able to useget_clonedwithout having to writeunsafemyself.It would be necessary to prevent a derived
CloneFromCellfrom calling a user-defined, potentially-problematicCloneimplementation. I can think of two ways to accomplish this.CloneFromCellwhen also derivingClone.Clonewhen cloning from a cell, and instead provide aunsafe fn clone_from_cell(*const self) -> Selfmethod onCloneFromCellas suggested above. That way the derivedCloneFromCellwould delegate directly toclone_from_cellon the fields without invoking theCloneimplementation at all.(1) seems less surprising (cloning won't ever behave different from a cell than normally), while (2) provides a bit more flexibility (e.g., perhaps individually cloning all of the fields works, so deriving
CloneFromCellwould be valid, but some of them can be skipped, so a customClonemakes sense).rkjnsn commentedon Feb 4, 2026
Actually, this got me thinking: could
#[derive(Clone)]use thetrivial_boundsfeature to automatically implCloneFromCelliff all of the fields areCloneFromCell?ChayimFriedman2 commentedon Aug 13, 2026
Should this return
Cell<T>orT? IMO returningCell<T>is weird, most callers will just immediately callinto_inner().ais523 commentedon Aug 13, 2026
The two are equally powerful (because you can write either in terms of the other, using
Cell::into_innerorCell::new), but at least in the cases where I've wanted to use this, returning aTis more natural than returning aCell<T>. We should probably be optimizing for the common case (and I expect wanting aTto be the common case, but we might need more people to chime in in order to get a representative sample).programmerjake commentedon Aug 13, 2026
iirc it is/was intended that it would just be
<Cell<T> as Clone>::cloneso that's why it returnsCell<T>instead ofTais523 commentedon Aug 13, 2026
I think it makes sense to add it as a method instead of (or as well as) a
Cloneimplementation: in the case where you just want a clone of the contents of the cell,cell.clone().into_inner()is a lot less readable thancell.get_cloned().Additionally, there are some problems with implementing it as a trait implementation (
Cellalready implementsClone, so if you have something that is bothCopyandCloneFromCell, the twoCloneimplementations would conflict with each other, and in extreme cases usingunsafe implmight be different). I think the restriction that an implementation of a stable trait on a stable type is insta-stable no longer exists, but if it does still exist, it would also be a problem.programmerjake commentedon Aug 14, 2026
we wanted to have
T: CopyimplyT: CloneFromCellvia a blanket impl or something, so you don't have the problem of which one to use, if it implsCopy, then both impls will have identical effectsChayimFriedman2 commentedon Aug 14, 2026
But then found it's unsound? Well, I don't think there's a way to work around the unsoundness (except maybe using specialization which is its own can of worms and also I'm not sure that really solves it), so it'll have to stay a method and therefore IMO should return
T.programmerjake commentedon Aug 14, 2026
iirc the unsoundness is around
CloneFromCopydoing weird stuff in userCloneimpls,Copydoesn't have that problem because we can just skip calling user code entirely.ChayimFriedman2 commentedon Aug 14, 2026
Yes, it's unsound to have
CloneFromCellimplemented for anyCopy, that's what I meant.ais523 commentedon Aug 14, 2026
The unsoundness is that not all
Copytypes can becloned from a cell, because they might have aCloneimplementation that does something other than copying them. This means that to avoid the unsoundness,.get_clonedwould have to be written in terms of copies forCopytypes but clones for types which aren'tCopy, which is specialization (and probably an unsound specialization, at that).Jules-Bertholet commentedon Aug 14, 2026
I have argued for a while that we should guarantee that specialization (and disagree that it is unsound). See Zulip discussion: https://rust-lang.zulipchat.com/#narrow/channel/219381-t-libs/topic/.60TrivialClone.60.20sadness/with/616436432
Plecra commentedon Oct 19, 2026
I know it's been a while, but I wanted to add to this.
I would strongly suggest that (3) is the library unsoundness: This is not respecting the invariants of
Rc, which requires that all shared references to it are viewing it at a compatible type (that is, that there does not exist a type in the program which believes it can write to the value of the Rc at a non-Rc type). Simply writing a type to a location isn't enough to justify taking a long-lived reference to that location. In this case, our&Cell<Rc>is only valid until the next possible relevant writeMy understanding is that the example was generally intended to justify this through this argument. Pointing the finger at 3 is still generally compatible with what you're saying - "next possible relevant write" is linked to the cell permission model you're talking about (I've been using something really similar, its cool to see it in the wild!)
I think there's an alternative way to approach the framing of the invariant -
For the duration of the call, the target is aliased as-if a &Self was live, and must assume that any memory that could contain it with Cell's invariants is temporarily substituted for shared access
note: In particular, this means it must only have effects permissible via shared access to the content of any such memory
(I dont know the specific wording being used in current memory model descriptions :) This is only intended as an idea of which objects to focus on, the details can change)
The above example remains unsound by the unjustified cast to
&Cell<Rc>- this safety comment isn't especially relevant, it's true that arbitrary&Cell<usize>s can't be held as&Cell<Rc<T>>s spanning code that could write to any aliased (therefore necessarily treated as Cell-invariants on this thread) memory.It's a little evasive to cite "Cell's invariants" without a specific definition, but in a strict sense they're well defined by whatever invariants are established by the Cell constructors. Also seems useful to note that (1) any Cells at more restrictive types couldnt be aliases and (2) any Cells at less restrictive types couldnt be aliases. Either way, a write of the supertype (in the memory sense,
Box<T: Sized> <: usize) could corrupt the other.