You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Stabilization -- the CoerceUnsized trait is unstable and we wish to revisit its design before stabilizing, so for now only stdlib types can be "unsized"
Is there anything that could be done to push this towards stabilization? I have some APIs in mind that would benefit significantly from the ability to bound on the Unsize trait.
I was idly wondering about this the other date with a type such as:
structFoo{flag:bool,data:str,}
Today there's not a huge amount of support for that but I noticed one oddity. So in theory the only way to instantiate such a type is to start with a concrete type, right? Let's say:
structFooConcrete{flag:bool,data:[u8;10],}
I would personally expect, then that if we had a function like:
fnfoo(drop_me:Box<Foo>){drop(drop_me);}
That function would simultaneously run the destructor for the underlying type and also free the memory. Currently, however, it only frees the memory and doesn't actually run any destructor. More interestingly this namely to me at least means that the Box<Foo> type needs two pieces of external data, both a vtable for the destructor but also a usize for the length of the dynamically sized data.
I'm not sure if this is just an unsupported use case perhaps? Or maybe support for this is planned elsewhere? Or maybe this isn't part of this issue at all?
Oh I mean when you implement the destructor for FooConcrete instead of Foo, that one isn't run. (your example gives a segfault locally for me as well...)
@alexcrichton transmuting Box::new((..., 10usize)) instead of Box::new((..., 10)) fixes the segfault (bug?).
Back on topic, Foo isn't a trait so I don't see why Box<Foo> should have a vtable; Box<Foo> is a concrete type (just an unsized one). After transmuting, Box<Foo> should (and does) forget it ever was a Box<FooConcrete>.
For me at least I'd expect Box<Foo> here to still run the destructor of the underlying type. It's not a trait object, yeah, but it's still erasing the underlying type. Otherwise this'd end up leaking resources, and would practically not be too useful I'd imagine?
(that being said I'm still not sure of the practicality of the Foo type anyway)
For me at least I'd expect Box here to still run the destructor of the underlying type. It's not a trait object, yeah, but it's still erasing the underlying type. Otherwise this'd end up leaking resources, and would practically not be too useful I'd imagine?
Why? They're totally unrelated types; FooConcrete isn't an underlying type, it's a different type. As a matter of fact, as-is, the code above is UB: rust could decide to lay out FooConcrete as ([u8; 10], bool) instead of (bool, [u8; 10]). Only Foo is guaranteed to have the data field come last (because DSTs must always come last).
IMO, the correct correct way to do this would be to allow box Foo { flag: true, data: *"my string" }, but, for now, I don't see any way to safely construct a Foo (without invoking UB).
I mean... I'm stating my opinion. Today the destructor isn't run. I'm trying to clarify if it should be run. I believe it should be run. If we don't run the destructor then to me this isn't a very useful feature in practice because that just means resources are leaked which tends to want to be avoided.
Of course, I should make sure to be portable to 64-bit architectures and add #[repr(C)] everywhere to make it valid :-).
I think the semantics of destructors are quite clear by this point. I have seen the pattern struct Foo([u8]); used as a newtype several times (because there is no header field, you can create it by directly transmuting a [u8]).
I don't think Unsize is useful without CoerceUnsized. Unsize is used for implementing CoerceUnsized, but the actual unsizing operation uses CoerceUnsized. I also expect manually implementing Unsize to either be forbidden or be able to cause compiler crashes or unsoundness.
I don't think Unsize is useful without CoerceUnsized. Unsize is used for implementing CoerceUnsized, but the actual unsizing operation uses CoerceUnsized. I also expect manually implementing Unsize to either be forbidden or be able to cause compiler crashes or unsoundness.
/// The command we are executing#[derive(Discriminant)]enumCmd{/// a zsh script to executeZsh,Bash,}#[async_trait]traitCommand{asyncfnexecute(&self,exec:Ctx,input:&str) -> anyhow::Result<String>;}#[async_trait]implCommandforZsh{asyncfnexecute(&self,exec:Ctx,input:&str) -> anyhow::Result<String>{// ...}}#[async_trait]implCommandforBash{asyncfnexecute(&self,exec:Ctx,input:&str) -> anyhow::Result<String>{// ...}}fnthis_requires_unsize(){let cmd1:Box<dynCommand> = Cmd::Zsh.cast();let cmd2:Box<dynCommand> = Cmd::Bash.cast();}
where Discriminant generates
implFrom<Zsh>forCmd{fnfrom(value:Zsh) -> Self{Self::Zsh}}impl std::convert::TryFrom<Cmd>forZsh{typeError = Cmd;fntry_from(value:Cmd) -> Result<Self,Self::Error>{ifletCmd::Zsh = value {Ok(Zsh)}else{Err(value)}}}#[doc = " a zsh script to execute"]structZsh;implFrom<Bash>forCmd{fnfrom(value:Bash) -> Self{Self::Bash}}impl std::convert::TryFrom<Cmd>forBash{typeError = Cmd;fntry_from(value:Cmd) -> Result<Self,Self::Error>{ifletCmd::Bash = value {Ok(Bash)}else{Err(value)}}}structBash;implCmd{fncast<U: ?Sized>(self) -> Box<U>whereZsh:::core::marker::Unsize<U>,Bash:::core::marker::Unsize<U>{let value = self;let value = matchZsh::try_from(value){Ok(v) => {let x = Box::new(v);return x;}Err(v) => v,};let value = matchBash::try_from(value){Ok(v) => {let x = Box::new(v);return x;}Err(v) => v,};unreachable!();}}
I'm curious why CoerceUnsized would prevent changing how dsts work. It's a trait with no methods. Could it be limited to derive, that is make it impossible for anyone to write a manual impl, and then the behavior could be changed later? That is to say that while the restriction today would be struct of one field etc. because no one can manually implement it, the restriction could be lifted later once the behavior is more defined and/or someone decides what to do about the dst general case.
My use case not that long ago is that I'm writing audio code which needs to never deallocate on the audio thread, and I wanted to have custom smart pointers which would defer deallocation to another thread in the background. They were also going to allocate objects from pools for cache friendliness. This can't be built on top of Arc and Weak without stabilizing custom allocators (and maybe not even then?) but this would have neatly solved the problem as far as I can tell. Instead, I dropped trying for it in favor of stdlib Arc and have moved to an architecture with custom 64-bit ids to do cross-thread addressing, wherein the user-side pieces are going to literally basically be Arc<u64>. Perhaps I will go back to it eventually, only with very specific unsizing ops of my own to dedicated traits.
Is anyone actively working on custom dsts? I keep seeing people saying it's important but the only use case I think I've seen is async traits. I don't find the "get size from C++ to put thing on the stack" use case that interesting because C++ itself doesn't really allow for that in the first place. As far as I know no popular language is talking about trying to e.g. put virtual classes on the stack. You can do it in C with alloca or gcc extensions but those just cause more problems than they're worth in my experience: suddenly you're open to stack overflows because of a math bug or whatever. And a similar thing with CStr that doesn't store length: now you've got possibly-implicit strlen all over.
I'm not saying "O dsts are useless" just that I see people talking about them as useful but never really having examples, and in
this case we have a general concrete usefulness for CoerceUnsized and it's easy to understand what that is.
Also, I see a soundness hole in many of the examples of custom dsts that get brought up from time to time in any case. They can make Rust unsound: if someone figures out how to get a value into the program they can read any pointer relative to the stack, at least for most schemes which would involve bumping sp or similar (and presumably anything more complex would have enough overhead that you'd go to the system allocator). It seems to me that custom dsts are years away but CoerceUnsized could probably be done in the very near future.
I don't find the "get size from C++ to put thing on the stack" use case that interesting because C++ itself doesn't really allow for that in the first place. As far as I know no popular language is talking about trying to e.g. put virtual classes on the stack.
Having Box<MyDst> also requires the length to be known at runtime as it is passed to the deallocation function.
For your use case can you have a type that is equivalent to Arc<[T]> rather than Arc<U>? That is specializing it to only work with slices rather than any (dynamically sized) type? In that case I don't think you need CoerceUnsized.
My use case wouldn't have been a slice, it would have been Arc<SineGenerator> to Arc<dyn Node> on top of my own non-std smart pointers, where the actual raw stored pointer pointed into a slab. It's been a bit since I tried to do it, but as I recall I gave up on a general solution because writing helpers to do the downcasting was getting gnarly. I'll revisit more specific solutions eventually but only after benchmarks etc; I thought I could produce a generally useful library which could be published to crates.io but it turned out I couldn't because I was running into combinations of unergonomic APIs and type constraints. It's a shame I didn't think to come here around the time I was doing it, if I had I'd have been able to articulate this better.
Which is a bit less ideal (the backpointers get in the way of a fully contiguous array) but workable with what we have here. It would of course be possible to go further and do pointer arithmetic and always align slab pages to OS pages, but that's kinda beside the point. these would have had Drop impls that pushed to a background thread if the slab page needed to go away, but that's also beside the point (tldr: you can write realtime-safe audio code if you assume reasonable limits for everything and glitch if someone makes the library have to allocate more on the audio thread).
Also I was conflating dsts on the stack with dsts in general which I realized a few minutes ago. I'm still not sure I see any particular benefit to extending the model or any reason why CoerceUnsized is incompatible with a more advanced model.
What is a realistic case wherein I'd be dealing with something that isn't effectively (length, dst)? If the length has to be passed to the deallocator, then it needs to always be known somehow or other. If the object came from C, then presumably it needs to be deallocated by C since Rust requires sizes and it's not guaranteed that Rust is using malloc.
Is the issue that we would want to coerce from struct MySizedType to dst MyUnsizedType where the compiler doesn't understand the coercion as it does slices/trait objects? E.g. the C trick of a header in the first field. I could sort of see that being a problem but it seems like a derive-only restriction for now lets it be extended later.
Is this intentional? The documentation for CoerceUnsized just says:
For wrapper types that directly embed T like Cell<T> and RefCell<T>, you can directly implement CoerceUnsized<Wrap<U>> for Wrap<T> where T: CoerceUnsized<U>.
From which I assume should allow T: ?Sized and U: ?Sized.
This prevents me from writing the following:
structInner<T: ?Sized>{meta:u32,value:RefCell<T>,}// This errors because `RefCell<T>` only allows `T: Sized, U: Sized`.impl<T: ?Sized,U: ?Sized>CoerceUnsized<Inner<U>>forInner<T>whereT:Unsize<U>{}let rc:Rc<Inner<i32>> = /* ... */;let rc = rc asRc<Inner<dynAny>>;let rc = Rc::downcast::<Inner<i32>>(rc).expect("Unable to cast");
I think U should at least be ?Sized (but I'm not sure if it makes sense for T to be ?Sized, given the lack of "super-fat" pointers currently (wider than 2 usizes))
My understanding is that for any given pair U and T with U: Unsize<T> and any value x of type U if we do ptr::metadata(&x as &T as *const T), this will yield the same value regardless of the choice of x. E.g., if U = [X; N] and T = [X] then the metadata is N regardless of the specific array of that length we choose. Similarly, if U is a concrete type implementing a trait and T is dyn Trait then the metadata is just a pointer to that trait's vtable, independent of the specific value x.
I.e., the metadata associated with &U as &T depends only on the types U and T and not on any runtime information, and so should probably be available somehow from just U, T where U: Unsize<T>. The simplest thing which comes to mind is to have Unsize<T> have an associated constant const METADATA: <T as Pointee>::Metadata. The other reasonable option is to do like for Sized and size_of where there's just a function in std::mem or std::ptr to fetch this metadata.
Hello! I'm hoping this is an appropriate venue for this question; let me know if it isn't. I've noticed that E0375, the error code for attempting to implement CoerceUnsized with multiple unsized fields, does not mention rustc's specific allowance of extra ("unsized") PhantomData fields, as described in rust-lang/rfcs#1234 and implemented in #28381.
Should that detail be included in that error code's description, and if so, should it be included before or after stabilization of CoerceUnsized?
Since CoerceUnsized does not indicate which type it coerces to, it seems not possible to implement methods like as_coerced for Option or Result. I generally find it difficult to find a fitting CoerceUnsized that allows as_coerced since we are missing higher order types.
Activity
steveklabnik commentedon Jan 27, 2015
I'm confused as to what this is about. isn't #18469 the tracking issue?
nrc commentedon Jan 27, 2015
Yes, this is a specific part of 18469. Seems useful to have it by itself since it stands alone (goes for the other issues too).
eternaleye commentedon Jan 14, 2017
Is there anything that could be done to push this towards stabilization? I have some APIs in mind that would benefit significantly from the ability to bound on the
Unsizetrait.alexcrichton commentedon Feb 6, 2017
I was idly wondering about this the other date with a type such as:
Today there's not a huge amount of support for that but I noticed one oddity. So in theory the only way to instantiate such a type is to start with a concrete type, right? Let's say:
I would personally expect, then that if we had a function like:
That function would simultaneously run the destructor for the underlying type and also free the memory. Currently, however, it only frees the memory and doesn't actually run any destructor. More interestingly this namely to me at least means that the
Box<Foo>type needs two pieces of external data, both a vtable for the destructor but also ausizefor the length of the dynamically sized data.I'm not sure if this is just an unsupported use case perhaps? Or maybe support for this is planned elsewhere? Or maybe this isn't part of this issue at all?
In any case just wanted to bring it up!
arielb1 commentedon Feb 6, 2017
What do you mean by the destructor not being called? I am quite sure it is:
EDIT:
Code above is buggy, better version:
alexcrichton commentedon Feb 6, 2017
Oh I mean when you implement the destructor for
FooConcreteinstead ofFoo, that one isn't run. (your example gives a segfault locally for me as well...)I'm specifically thinking of something like this:
where the IR is:
Notably there's some concrete type behind
Box<Foo>, so presumably some dynamic call to a destructor needs to be made, but in this case it isn't :(Stebalien commentedon Feb 6, 2017
@alexcrichton transmuting
Box::new((..., 10usize))instead ofBox::new((..., 10))fixes the segfault (bug?).Back on topic,
Fooisn't a trait so I don't see whyBox<Foo>should have a vtable;Box<Foo>is a concrete type (just an unsized one). After transmuting,Box<Foo>should (and does) forget it ever was aBox<FooConcrete>.alexcrichton commentedon Feb 6, 2017
@Stebalien ah yes that does indeed fix it!
For me at least I'd expect
Box<Foo>here to still run the destructor of the underlying type. It's not a trait object, yeah, but it's still erasing the underlying type. Otherwise this'd end up leaking resources, and would practically not be too useful I'd imagine?(that being said I'm still not sure of the practicality of the
Footype anyway)Stebalien commentedon Feb 6, 2017
Why? They're totally unrelated types;
FooConcreteisn't an underlying type, it's a different type. As a matter of fact, as-is, the code above is UB: rust could decide to lay outFooConcreteas([u8; 10], bool)instead of(bool, [u8; 10]). OnlyFoois guaranteed to have thedatafield come last (because DSTs must always come last).IMO, the correct correct way to do this would be to allow
box Foo { flag: true, data: *"my string" }, but, for now, I don't see any way to safely construct aFoo(without invoking UB).alexcrichton commentedon Feb 6, 2017
I mean... I'm stating my opinion. Today the destructor isn't run. I'm trying to clarify if it should be run. I believe it should be run. If we don't run the destructor then to me this isn't a very useful feature in practice because that just means resources are leaked which tends to want to be avoided.
Stebalien commentedon Feb 6, 2017
I just don't understand why it should be run. Would you expect the following to print "dropping bar" (it currently prints "dropping baz")?:
The only difference is that
Bazis sized.arielb1 commentedon Feb 6, 2017
@alexcrichton
Of course, I should make sure to be portable to 64-bit architectures and add
#[repr(C)]everywhere to make it valid :-).I think the semantics of destructors are quite clear by this point. I have seen the pattern
struct Foo([u8]);used as a newtype several times (because there is no header field, you can create it by directly transmuting a[u8]).17 remaining items
bjorn3 commentedon Apr 2, 2023
I don't think
Unsizeis useful withoutCoerceUnsized.Unsizeis used for implementingCoerceUnsized, but the actual unsizing operation usesCoerceUnsized. I also expect manually implementingUnsizeto either be forbidden or be able to cause compiler crashes or unsoundness.andrewgazelka commentedon Apr 2, 2023
This is a use case where
Unsizeis useful @bjorn3https://github.com/getcollective-ai/collective/blob/d69c7c1549b6efde48529cd21bd5159e6a48160c/code/executor/src/command.rs#L35-L38
Useful snippets:
where
Discriminantgeneratesahicks92 commentedon Apr 15, 2023
I'm curious why CoerceUnsized would prevent changing how dsts work. It's a trait with no methods. Could it be limited to derive, that is make it impossible for anyone to write a manual impl, and then the behavior could be changed later? That is to say that while the restriction today would be struct of one field etc. because no one can manually implement it, the restriction could be lifted later once the behavior is more defined and/or someone decides what to do about the dst general case.
My use case not that long ago is that I'm writing audio code which needs to never deallocate on the audio thread, and I wanted to have custom smart pointers which would defer deallocation to another thread in the background. They were also going to allocate objects from pools for cache friendliness. This can't be built on top of
ArcandWeakwithout stabilizing custom allocators (and maybe not even then?) but this would have neatly solved the problem as far as I can tell. Instead, I dropped trying for it in favor of stdlibArcand have moved to an architecture with custom 64-bit ids to do cross-thread addressing, wherein the user-side pieces are going to literally basically beArc<u64>. Perhaps I will go back to it eventually, only with very specific unsizing ops of my own to dedicated traits.Is anyone actively working on custom dsts? I keep seeing people saying it's important but the only use case I think I've seen is async traits. I don't find the "get size from C++ to put thing on the stack" use case that interesting because C++ itself doesn't really allow for that in the first place. As far as I know no popular language is talking about trying to e.g. put virtual classes on the stack. You can do it in C with alloca or gcc extensions but those just cause more problems than they're worth in my experience: suddenly you're open to stack overflows because of a math bug or whatever. And a similar thing with
CStrthat doesn't store length: now you've got possibly-implicit strlen all over.I'm not saying "O dsts are useless" just that I see people talking about them as useful but never really having examples, and in
this case we have a general concrete usefulness for
CoerceUnsizedand it's easy to understand what that is.Also, I see a soundness hole in many of the examples of custom dsts that get brought up from time to time in any case. They can make Rust unsound: if someone figures out how to get a value into the program they can read any pointer relative to the stack, at least for most schemes which would involve bumping sp or similar (and presumably anything more complex would have enough overhead that you'd go to the system allocator). It seems to me that custom dsts are years away but
CoerceUnsizedcould probably be done in the very near future.bjorn3 commentedon Apr 15, 2023
Having
Box<MyDst>also requires the length to be known at runtime as it is passed to the deallocation function.For your use case can you have a type that is equivalent to
Arc<[T]>rather thanArc<U>? That is specializing it to only work with slices rather than any (dynamically sized) type? In that case I don't think you needCoerceUnsized.ahicks92 commentedon Apr 15, 2023
My use case wouldn't have been a slice, it would have been
Arc<SineGenerator>toArc<dyn Node>on top of my own non-std smart pointers, where the actual raw stored pointer pointed into a slab. It's been a bit since I tried to do it, but as I recall I gave up on a general solution because writing helpers to do the downcasting was getting gnarly. I'll revisit more specific solutions eventually but only after benchmarks etc; I thought I could produce a generally useful library which could be published to crates.io but it turned out I couldn't because I was running into combinations of unergonomic APIs and type constraints. It's a shame I didn't think to come here around the time I was doing it, if I had I'd have been able to articulate this better.Essentially, I wanted to do:
It would have had to be:
Which is a bit less ideal (the backpointers get in the way of a fully contiguous array) but workable with what we have here. It would of course be possible to go further and do pointer arithmetic and always align slab pages to OS pages, but that's kinda beside the point. these would have had Drop impls that pushed to a background thread if the slab page needed to go away, but that's also beside the point (tldr: you can write realtime-safe audio code if you assume reasonable limits for everything and glitch if someone makes the library have to allocate more on the audio thread).
Also I was conflating dsts on the stack with dsts in general which I realized a few minutes ago. I'm still not sure I see any particular benefit to extending the model or any reason why
CoerceUnsizedis incompatible with a more advanced model.What is a realistic case wherein I'd be dealing with something that isn't effectively
(length, dst)? If the length has to be passed to the deallocator, then it needs to always be known somehow or other. If the object came from C, then presumably it needs to be deallocated by C since Rust requires sizes and it's not guaranteed that Rust is using malloc.Is the issue that we would want to coerce from
struct MySizedTypetodst MyUnsizedTypewhere the compiler doesn't understand the coercion as it does slices/trait objects? E.g. the C trick of a header in the first field. I could sort of see that being a problem but it seems like a derive-only restriction for now lets it be extended later.Veykril commentedon Apr 15, 2023
Fwiw, I've been experimenting with Unsize and CoerceUnsized recently, see https://github.com/ferrous-systems/unsize-experiments. I was planning on trying to write down my findings there as a pre-RFC next week.
Veykril commentedon May 17, 2023
Fwiw, I posted a pre-RFC on irlo: https://internals.rust-lang.org/t/pre-rfc-flexible-unsize-and-coerceunsize-traits/18789
Zenithsiz commentedon Feb 5, 2024
The commit that implemented
CoerceUnsizedfor{Cell, RefCell, UnsafeCell}seems to not specify?Sizedon the type parameters.Is this intentional? The documentation for
CoerceUnsizedjust says:From which I assume should allow
T: ?SizedandU: ?Sized.This prevents me from writing the following:
Which I think should be fine.
fogti commentedon Feb 6, 2024
I think
Ushould at least be?Sized(but I'm not sure if it makes sense forTto be?Sized, given the lack of "super-fat" pointers currently (wider than 2usizes))pczarn commentedon Jun 1, 2024
I'd love to see this stabilized so that we may comfortably use DSTs in the
bit-veccrate, in particular for this feature: contain-rs/bit-vec#46core: add `portable-atomic` support (#3199)
yaksher commentedon May 22, 2025
My understanding is that for any given pair
UandTwithU: Unsize<T>and any valuexof typeUif we doptr::metadata(&x as &T as *const T), this will yield the same value regardless of the choice ofx. E.g., ifU = [X; N]andT = [X]then the metadata isNregardless of the specific array of that length we choose. Similarly, ifUis a concrete type implementing a trait andTisdyn Traitthen the metadata is just a pointer to that trait's vtable, independent of the specific valuex.I.e., the metadata associated with
&U as &Tdepends only on the typesUandTand not on any runtime information, and so should probably be available somehow from justU, T where U: Unsize<T>. The simplest thing which comes to mind is to haveUnsize<T>have an associated constantconst METADATA: <T as Pointee>::Metadata. The other reasonable option is to do like forSizedandsize_ofwhere there's just a function instd::memorstd::ptrto fetch this metadata.samcowger commentedon Jun 27, 2025
Hello! I'm hoping this is an appropriate venue for this question; let me know if it isn't. I've noticed that
E0375, the error code for attempting to implementCoerceUnsizedwith multiple unsized fields, does not mentionrustc's specific allowance of extra ("unsized")PhantomDatafields, as described in rust-lang/rfcs#1234 and implemented in #28381.Should that detail be included in that error code's description, and if so, should it be included before or after stabilization of
CoerceUnsized?phisn commentedon Nov 4, 2025
Since
CoerceUnsizeddoes not indicate which type it coerces to, it seems not possible to implement methods likeas_coercedforOptionorResult. I generally find it difficult to find a fittingCoerceUnsizedthat allowsas_coercedsince we are missing higher order types.core: add `portable-atomic` support (tokio-rs#3199)