Open
Description
Feature gate: #![feature(clone_to_uninit)]
This is a tracking issue for CloneToUninit
and its impls. This trait backs the behavior of Rc::make_mut
and Arc::make_mut
, and likely in the future also Box::clone
.
Public API
// core::clone
pub unsafe trait CloneToUninit {
unsafe fn clone_to_uninit(&self, dst: *mut Self);
}
unsafe impl<T: Clone> CloneToUninit for T;
unsafe impl<T: Clone> CloneToUninit for [T];
// TODO:
// unsafe impl CloneToUninit for str;
// unsafe impl CloneToUninit for CStr;
// unsafe impl CloneToUninit for OsStr;
// unsafe impl CloneToUninit for Path;
Steps / History
- Implementation: Generalize
{Rc,Arc}::make_mut()
to unsized types. #116113ConvertBox::clone
to be based onCloneToUninit
Final comment period (FCP)1Stabilization PR
Unresolved Questions
- Consider using
*mut ()
in the signature instead of*mut Self
. This makes CloneToUninit dyn-safe. Generalize{Rc,Arc}::make_mut()
to unsized types. #116113 (comment)
Footnotes
Metadata
Metadata
Assignees
Labels
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
{Rc,Arc}::make_mut()
to unsized types. #116113dtolnay commentedon Jun 22, 2024
@kpreid #116113 (comment)
I thought about what a safer primitive could be that still enables what we need in
make_mut
. I think it might (almost?) exists already:where
RcAllocator<A: Allocator>
is an allocator whose every allocation points to thevalue
part of aMaybeUninit<RcBox<T>>
. When you ask it to allocate with the Layout of some typeT
, it delegates to the underlying allocator to allocate the Layout ofRcBox<T>
and offsets that pointer.rust/library/alloc/src/rc.rs
Lines 286 to 291 in fcae626
Then
make_mut
works by turning the pointer held by theRc<T, A>
into aBox<T, RcAllocator<A>>
, cloning it, and turning theBox<T, RcAllocator<A>>
back intoRc<T, A>
.Instead of implementing an unsafe trait
CloneToUninit
, types plug intomake_mut
support by implementing the safe traitClone
forBox<MyType, A> where A: Allocator
. The concrete allocator typeRcAllocator
itself is not public; your impls are written using a genericA
as they already universally are. Notice no unsafe in this impl!rust/library/alloc/src/boxed.rs
Lines 2218 to 2222 in fcae626
What I don't know is whether
Box
's use ofUnique
throws a wrench in the works. As I currently understand it, I think it does not. CastingRc
'sNonNull<RcBox<T>>
intoBox<T, RcAllocator<A>>
in order to clone it would not be sound, but I also think that's not necessary for the above to work. If we can rearrangeRc
's layout a bit to achieve a guarantee thatRc<T, A>
has the same layout asBox<T, RcAllocator<A>>
, then we'd only be casting&Rc<T, A>
to&Box<T, RcAllocator<A>>
to pass into the clone impl, and as such,Unique
never comes into play. Obviously I can have as many&Box<T>
aliasing one another's allocations as I want. An actual ownedBox<T, RcAllocator<A>>
would never exist except for the value that becomes the return value of theclone
, during which it is unique. I think it all works out as required, but I wouldn't be surprised if I am missing something.kpreid commentedon Jun 22, 2024
One thing that this doesn't help with, that the current
CloneToUninit
does, is cloning into a different container type. I'm not sure if that's useful enough to be worthwhile, but it feels to me like it might be an important piece of a future Rust where there are fewer obstacles to using DSTs, which is one of the things I hoped to do by having (a safer version of)CloneToUninit
be public.the8472 commentedon Jun 22, 2024
Tangentially, a generic way to build a container type and then clone or write into its uninitialized payload slot would fit nicely into a solution for rust-lang/wg-allocators#90
dtolnay commentedon Jun 24, 2024
https://doc.rust-lang.org/nightly/std/clone/trait.CloneToUninit.html#implementors
I think we should consider adding
#[doc(hidden)]
to theT: Copy
specializations. They are just distracting when it comes to documenting the public API of this trait.the8472 commentedon Jun 24, 2024
The std-dev-guide says we should use private specialization traits, which we do in most places. The implementation PR promoted a private trait to a public one, so it should have changed the specialization to a subtrait.
GrigorenkoPV commentedon Jun 25, 2024
I have implemented this suggestion in #126877 (scope creep, yay).
Also, this has an additional benefit of no longer needing any
#[doc(hidden)]
to hide the mess:UniqueRc
/UniqueArc
#112566Rollup merge of rust-lang#126877 - GrigorenkoPV:clone_to_uninit, r=dt…
Auto merge of rust-lang#126877 - GrigorenkoPV:clone_to_uninit, r=dtolnay
Auto merge of rust-lang#126877 - GrigorenkoPV:clone_to_uninit, r=dtolnay
lolbinarycat commentedon Aug 26, 2024
why does this take
*mut Self
instead of&MaybeUninit<Self>
?zachs18 commentedon Aug 26, 2024
&mut MaybeUninit<Self>
, not&MaybeUninit<Self>
, so that it can be written into.MaybeUninit<T>
(currently) only supportsT: Sized
(and AFAIK there is not yet any propopsed RFC to expandMaybeUninit
or unions in general to supportT: ?Sized
fields), which makes this not work for (one of) the main motivating features:Arc/Rc::make_mut
on slices.clone_to_uninit
should take*mut ()
instead of*mut Self
anyway, so that the trait is object-safe (listed as an unresolved question above).a. It could take something like
&mut [MaybeUninit<u8>]
to be object-safe but still allow size-checking and in some cases safe writing, I suppose, but perhaps just having a separateexpected_layout: Layout
parameter or similar would be better, though that would only be useful in debug builds anyway, since if they were wrong it would by the contract of the trait already be UB.CloneToUninit
documentation. #133055Box::new_from_ref
for making aBox<T>
from a&T
whereT: CloneToUninit + ?Sized
(andRc
andArc
) rust-lang/libs-team#483kpreid commentedon Jan 11, 2025
One of the implementation steps listed in this issue is “Convert
Box::clone
to be based onCloneToUninit
”. I thought I’d try implementing it, but that turns out to be tricky, because<Box<T> as Clone>::clone_from()
exists. While bothclone_from()
andclone_to_uninit()
allow in-place clones,clone_from()
(implicitly) promises that if it panics, the destination is still a valid value, whereasclone_to_uninit()
does not promise that, and cannot reuse nested allocations asclone_from()
can. This means that<Box<T> as Clone>::clone_from()
cannot be implemented in terms ofT as CloneToUninit
, except in the default no-reuse way.To solve this problem, I tried writing a specialization trait which used
clone_from
whenT: Clone
andclone_to_uninit()
into a new box otherwise, but the compiler tells mewhich I read is a restriction to prevent lifetime specialization unsoundness, so this seems like an unsolvable problem, but I’m not particularly familiar with tricks for stdlib specialization. I’m posting this comment in the hopes that someone else can come up with a way that does work without regressing
Box::clone_from()
. The goal ofCloneToUninit
isn't having exactlyclone_to_uninit()
, but being able to clone DSTs, so perhaps someone can come up with an improvedCloneToUninit
that — also ‘clones to init’, somehow? Perhaps it should be an optional method just likeClone
has the optionalclone_from()
— but how to do that without forcing extra allocations for unwind safety?zachs18 commentedon Jan 12, 2025
One thing I tried was making
CloneToUninit
instead beCloneUnsized
with an additionalclone_from_unsized(&mut self, src: *const u8)
method that, on panic, guarantees that*self
is still valid, but may have changed (e.g. if cloning the third element of a length-5 slice panics, then the first and second elements may have been successfully cloned intoself
.Then, we can have a single
impl<T: ?Sized + CloneUnsized, A: Allocator> for Box<T, A>
, whereBox::clone_from
can check if the metadata and layouts are the same, and if so callCloneUnsized::clone_from_unsized
without allocating, otherwise just calling*self = Box::new_from_ref(&**src);
(which creates aBox<T>
from cloning a&T
whereT: CloneUnsized
)master...zachs18:rust:clone_unsized (ignore the last "stash" commit, that was something additional I was trying)
This seems to work well, but does complicate the API somewhat.
ByteStr
andByteString
types #135073Rollup merge of rust-lang#133055 - kpreid:clone-uninit-doc, r=Mark-Si…
Unrolled build for rust-lang#133055
bal-e commentedon Mar 19, 2025
I copied the
CloneToUninit
code into my own codebase to try it out, and I noticed an important limitation: it's really hard to use this trait (in stable code). Consider a hypotheticalBox::clone_unsized_from
:If
T
is unsized, a pointer/reference toT
includes metadata, and copying metadata fromvalue
while changing the address toptr
is really hard to do. In nightly, we havepointer::with_metadata_of()
, but I'm not aware of the expected stabilization timeline of the pointer metadata features.I do have a workaround, but it is not dyn-compatible. I added a new method,
fn ptr_with_address(&self, addr: *mut ()) -> *mut Self
. It's relatively easy for every type implementingCloneToUninit
to implement this method, but users of those types can't do it themselves.Now
Box::clone_unsized_from
can just callvalue.ptr_with_address(ptr)
to get a pointer with the right metadata.Is the plan to stabilize
CloneToUninit
after the relevant pointer metadata features are stabilized? Otherwise, is it acceptable that third-party container types (in stable code) can't make use of it until those features are stabilized?Rollup merge of rust-lang#133055 - kpreid:clone-uninit-doc, r=Mark-Si…