RFC: `Aligned` trait
I also suggested having Aligned since it would allow offset_of! to work on struct fields that are slices/str (aligned but unsized):
https://github.com/rust-lang/rfcs/pull/3308#issuecomment-1232875837
https://github.com/rust-lang/rfcs/pull/3308#issuecomment-1232880185
Yeah, this would be nice for offset_of!. I don't know if it's worth the additional language complexity, but unlike most proposals that extend ?Foo traits, this seems fairly minimal
So, one slight callout that I think might be worth considering for a future edition.
I think that ?Sized + Aligned is rather weird, and I think that we should have this be the meaning of ?Sized and ?Aligned opt out of both. However, this would break a lot of existing code, which is why I recommend it happening at an edition boundary.
I also think that ?Aligned should work on all editions to be equivalent to ?Sized today.
However, this is just my opinion on the matter, and changing everyone's muscle-memory for ?Sized might not be worth it. In this case, we would just make ?Aligned have an auto-fix for ?Sized.
There may have also been discussion on this already that I didn't see in the RFC, in which case that's fine and we should include it. But I think we should at least have some stance on this.
Discussion of ?Aligned added to the RFC. As stated there, I don't think we should add ?Aligned to the current edition, it would just be redundant and cause even more confusion. I have no objection to doing so in a later edition, but I've left that as a future possibility for now.
I think having the "only aligned" version be ?Sized + Aligned is a nice way around the compatibility change. Using ?Aligned doesn't seem worth the churn that it would take to me.
Yeah, it's entirely reasonable to just say it's not worth the effort, although I think it might be worth considering for completeness. I personally think it would be a positive change as a way of pushing people to think more about what kinds of types they want to allow in their APIs, but it could also be another barrier to learning the language for others. I just want to make sure that it was mentioned in the discussion so people can think about whether they prefer ?Aligned and ?Sized versus ?Sized and ?Sized + Aligned.
Another factor in this discussion is that new opt-out traits might be added in the future (like DynSized). That's why I don't think we should make decisions about syntax in future editions now; we don't know how the other proposed traits will play out and I want this RFC to remain independent of those discussions.
Added note about offset_of: if Rust ever gets structs with multiple unsized fields, these could be Aligned but not fully support offset_of.
I'd like to see some consideration on how this interacts with the pointer metadata APIs? In particular, it is being considered in https://github.com/rust-lang/rust/issues/81513 to add Thin as a supertrait of Sized.
I'd like to see some consideration on how this interacts with the pointer metadata APIs? In particular, it is being considered in rust-lang/rust#81513 to add
Thinas a supertrait ofSized.
I would expect Aligned to just be another supertrait of Sized and not be a super/subtrait of Thin, there are fat Aligned types such as [T] and Thin + Aligned types that we may get in the future:
#[repr(C)]
struct PascalString {
len: u8,
#[thin]
data: [u8],
}
There are also fat !Aligned types such as dyn Send and Thin + !Aligned types such as extern types.
Currently, this RFC leaves adding Aligned to a future edition's prelude as a future possibility. But Aligned has no associated methods or items, so I think adding it to every edition's prelude actually would not break any code. Or am I wrong about that?
Currently, this RFC leaves adding
Alignedto a future edition's prelude as a future possibility. ButAlignedhas no associated methods or items, so I think adding it to every edition's prelude actually would not break any code. Or am I wrong about that?
Prior art for this: Unpin is in every prelude for presumably similar reasons. So, it's not off the table, but I'm sure some folks would like to do a crater run beforehand.
I've proposed that Aligned be added to the prelude immediately.
@Jules-Bertholet can you elaborate on what is enabled by this trait? The RFC says
Data structures and containers that wish to store unsized types are easier to implement if they can produce a dangling, well-aligned pointer to the unsized type. Being able to determine a type's alignment without a value of the type allows doing this.
but I was hoping for a more detailed example, or a link.
@nikomatsakis I've added a link to unsized-vec, which is the crate that originally motivated me to write this RFC. unsized-vec provides UnsizedVec<T>, which is essentially equivalent to Vec<T>, except that T has no Sized bound.
The specific "dangling pointer" use-case that the RFC originally cited turns out to have a much simpler solution that doesn't require Aligned, but unsized-vec uses (an imperfect library emulation of) Aligned in many other ways. Basically, non-Aligned types require a lot of complicated special handling, and being able to skip that when it's not necessary allows both better performance and better APIs.
unsized-vec has been entirely rewritten since this RFC was first posted. Compare aligned.rs to unaligned.rs to see how the Aligned trait is used.
This may be off-topic but I would still want point out a possible extension for the alignment of the unsized types.
Currently custom DST has this problem of having to dynamically calculate the offset for its unsized field due to its unknown alignment. (https://github.com/rust-lang/rust/issues/106683#issuecomment-1381281874). However, from a zero-cost abstraction point of view, if we can somehow guarantee the alignment of the unsized type to be the same value or to be a large enough value (which is quite common), this offset calculation should not be necessary.
This would involve complex interactions with the type representations and I don't expect it to come with this RFC. But still worth to explore for custom DST.
See also the lang-team notes around DynSized (disclaimer: that I authored). In those notes I define four classes of types:
- "
T: Sized + MetaSized + DynSized", where the size and alignment are known statically; - "
T: ?Sized + MetaSized + DynSized", where the size and alignment are known from the pointee metadata[^1]; - "
T: ?Sized + ?MetaSized + DynSized", where the size and alignment may require reading the pointee; and - "
T: ?Sized + ?MetaSized + ?DynSized", where the size and alignment cannot be determined by (generic) code.
[^1]: The notes don't make a decision on whether MetaSized is allowed to know the address of the pointee. Given Rust types are generally address-insensitive, and using an unsized type as a tail field requires knowing alignment in order to even get the data pointer, I'm currently of the belief that giving access to the data pointer without permission to read it is an unnecessarily exotic use case. I can't think of a case which wouldn't make it unsound to own an instance of the type in a Rust allocated object.
Aligned is interesting because it makes the correct observation that splitting discussion of knowing size from knowing alignment is useful. I am extremely uncertain whether making this split for the other two classes of knowable layout (requires metadata, requires reading) is ever useful, or if it's ever useful to make size easier to know than alignment, but this is something to consider in the future. Plus, as ZhennanWu notes, knowing constant alignment can potentially improve code generation for dynamically sized (but constant aligned) types (though, since Aligned isn't object-safe, it'd need to be Aligned<4> or similar to be used for trait objects, and constant folding handles the meta-sized-but-actually-constant just fine).
Aligned is also interesting because -- unlike DynSized -- it doesn't need to introduce a new ?Bound into the language. Instead, ?Sized serves to opt-out of both Sized and Aligned bounds, and if you want just Aligned you write ?Sized + Aligned. I believe MetaSized is the same way (size_of_val gets a readable reference, and the unstable size_of_val_raw is carefully future-proofed to unsafely restrict itself to MetaSized types (plus extern type's current MetaSized bodge)), but DynSized does relax further than ?Sized relaxes today.
I have a few suggestions for things I think the RFC should note:
- Note
?DynSizedas a future extension of talking about unsized types, even if only to note thatAlignedhas little/no implications on. - Note as an alternative making
Alignedbehave like any other not-object-safe bound and be future incompatible with dyn trait, rather than giving it theSizedmagic which only applies to aSizedsupertrait. - Note as a future/alternative allowing a type to be
Sized + ?Aligned, or argue that this is a nonuseful capability[^3].
It's not necessary to add these, but I think it's an improvement to mention these explicitly.
[^3]: FWIW, I do think it's reasonable to say that if you know the size you should be able to know the required alignment as well. The only benefit I can see to knowing size without alignment is that SB retagging can retag all of the bytes only knowing size. Thus the fully complete DynSized graph would give a default (?OptOut) bound of Sized + DynSized for generics, a default bound of DynSized for traits, and Sized: Aligned + MetaSized, Aligned: MetaAligned, MetaSized: MetaAligned + DynSized, MetaAligned: DynAligned, DynSized: DynAligned, and DynAligned: {nothing}.
off-topic
if we can somehow guarantee the alignment of the unsized type to be the same value or to be a large enough value (which is quite common)
This can't be done in general, because we allow increasing alignment arbitrarily high. However, it could be allowed to say trait MyTrait: Aligned<16> or similar, such that all implementers of MyTrait are known to be aligned to 16.
It's not possible to increase alignment for the trait object but not for the concrete type, though, or at least not without breaking the ability to actually create that trait object via unsizing, because it's necessary to be able to go from &WithTail<u8> to &WithTail<dyn MyTrait> that this doesn't change the pointee layout at all.
@CAD97 I've adressed your comments.
With the offset_of! RFC merged, I've moved support for said macro from a future possibility to the core proposal.
As a datapoint: we actually have an Aligned trait in the compiler internals:
https://github.com/rust-lang/rust/blob/f9a6b71580cd53dd4491d9bb6400f7ee841d9c22/compiler/rustc_data_structures/src/aligned.rs#L22
(it's a bit different from this rfc due to not having language support, but has the same role)
@Jules-Bertholet we discussed this in the @rust-lang/lang planning meeting and we were concerned that the RFC didn't really have much for motivation. @workingjubilee mentioned that there was strong motivation, but it wasn't really present in the RFC. Do you think you could update it?
I've added an explanation of how unsized-vec uses Aligned to the motivation section.