rfcs icon indicating copy to clipboard operation
rfcs copied to clipboard

RFC: `Aligned` trait

Open Jules-Bertholet opened this issue 3 years ago • 23 comments

Rendered

Add an Aligned trait to core::marker, as a supertrait of Sized.

Implementation

Jules-Bertholet avatar Sep 24 '22 16:09 Jules-Bertholet

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

programmerjake avatar Sep 25 '22 04:09 programmerjake

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

thomcc avatar Sep 25 '22 07:09 thomcc

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.

clarfonthey avatar Sep 25 '22 20:09 clarfonthey

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.

Jules-Bertholet avatar Sep 25 '22 22:09 Jules-Bertholet

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.

thomcc avatar Sep 25 '22 22:09 thomcc

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.

clarfonthey avatar Sep 25 '22 22:09 clarfonthey

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.

Jules-Bertholet avatar Sep 25 '22 22:09 Jules-Bertholet

Added note about offset_of: if Rust ever gets structs with multiple unsized fields, these could be Aligned but not fully support offset_of.

Jules-Bertholet avatar Sep 29 '22 04:09 Jules-Bertholet

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.

madsmtm avatar Sep 30 '22 11:09 madsmtm

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 Thin as a supertrait of Sized.

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.

programmerjake avatar Sep 30 '22 12:09 programmerjake

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?

Jules-Bertholet avatar Oct 09 '22 03:10 Jules-Bertholet

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?

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.

clarfonthey avatar Oct 10 '22 22:10 clarfonthey

I've proposed that Aligned be added to the prelude immediately.

Jules-Bertholet avatar Oct 10 '22 23:10 Jules-Bertholet

@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 avatar Oct 11 '22 15:10 nikomatsakis

@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.

Jules-Bertholet avatar Oct 11 '22 17:10 Jules-Bertholet

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.

Jules-Bertholet avatar Nov 16 '22 21:11 Jules-Bertholet

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.

ZhennanWu avatar Jan 19 '23 06:01 ZhennanWu

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 ?DynSized as a future extension of talking about unsized types, even if only to note that Aligned has little/no implications on.
  • Note as an alternative making Aligned behave like any other not-object-safe bound and be future incompatible with dyn trait, rather than giving it the Sized magic which only applies to a Sized supertrait.
  • 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 avatar Jan 19 '23 19:01 CAD97

@CAD97 I've adressed your comments.

Jules-Bertholet avatar Feb 22 '23 00:02 Jules-Bertholet

With the offset_of! RFC merged, I've moved support for said macro from a future possibility to the core proposal.

Jules-Bertholet avatar May 05 '23 22:05 Jules-Bertholet

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)

WaffleLapkin avatar May 05 '23 23:05 WaffleLapkin

@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?

nikomatsakis avatar Oct 04 '23 17:10 nikomatsakis

I've added an explanation of how unsized-vec uses Aligned to the motivation section.

Jules-Bertholet avatar Nov 15 '23 06:11 Jules-Bertholet