From 45d9eaccf2f63fedef460be3751a8997690ff7cd Mon Sep 17 00:00:00 2001 From: David Wood Date: Fri, 4 Oct 2024 17:55:46 +0100 Subject: [PATCH 01/45] hierarchy of sized traits --- text/0000-sized-hierarchy.md | 877 +++++++++++++++++++++++++++++++++++ 1 file changed, 877 insertions(+) create mode 100644 text/0000-sized-hierarchy.md diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md new file mode 100644 index 00000000000..5f6b95d7288 --- /dev/null +++ b/text/0000-sized-hierarchy.md @@ -0,0 +1,877 @@ +- Feature Name: `sized_hierarchy` +- Start Date: 2024-09-30 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +# Summary +[summary]: #summary + +All of Rust's types are either *sized*, which implement the `Sized` trait and +have a statically computable size during compilation, or *unsized*, which do not +implement the `Sized` trait and are assumed to have a size which can be computed +at runtime. However, this dichotomy misses two categories of type - types whose +size is unknown during compilation but statically known at runtime, and types +whose size can never be known. Supporting the former is a prerequisite to stable +scalable vector types and supporting the latter is a prerequisite to unblocking +extern types. + +*The ideas in this RFC are heavily inspired by and borrow from blog posts and +previous RFCs which have proposed similar changes to the language, see +[Prior Art](#prior-art) for a full list with appropriate attributions.* + +# Background +[background]: #background + +Rust has the `Sized` marker trait which indicates that a type's size is +statically known at compilation time. `Sized` is an trait which is automatically +implemented by the compiler on any type that has a statically known size. All +type parameters have a default bound of `Sized` and `?Sized` syntax can be used +to remove this bound. + +Due to `size_of_val::`'s `T: ?Sized` bound, it is expected that the size of a +`?Sized` type can be computable at runtime, and therefore a `T` with `T: ?Sized` +cannot be a type with no size. + +# Motivation +[motivation]: #motivation + +Introducing a hierarchy of `Sized` traits resolves blockers for other RFCs which +have had significant interest. + +## Runtime-sized types and scalable vector types +[runtime-sized-types-and-scalable-vector-types]: #runtime-sized-types-and-scalable-vector-types + +Rust already supports [SIMD][rfc_simd] (*Single Instruction Multiple Data*), +which allows operating on multiple values in a single instruction. Processors +have SIMD registers of a known, fixed length and a variety of intrinsics +which operate on these registers. For example, x86-64 introduced 128-bit SIMD +registers with SSE, 256-bit SIMD registers with AVX, and 512-bit SIMD registers +with AVX-512, and Arm introduced 128-bit SIMD registers with Neon. + +As an alternative to releasing SIMD extensions with greater bit widths, Arm and +RISC-V have vector extensions (SVE/Scalable Vector Extension and the "V" Vector +Extension respectively) where the bit width of vector registers depends on the +CPU implementation, and the instructions which operate these registers are bit +width-agnostic. + +As a consequence, these types are not `Sized` in the Rust sense, as the size of +a scalable vector cannot be known during compilation, but is statically known at +runtime. However, these are value types which should implement `Copy` and can be +returned from functions, can be variables on the stack, etc. These types should +implement `Copy` but given that `Copy` is a supertrait of `Sized`, they cannot +be `Copy` without being `Sized`, and aren't `Sized`. + +Introducing a hierarchy of `Sized` traits will enable `Copy` to be a supertrait +of the trait for types whose size is known statically at runtime, and therefore +enable these types to be `Copy` and function correctly without special cases in +the type system. See [rfcs#3268: Scalable Vectors][rfc_scalable_vectors]. + +## Unsized types and extern types +[unsized-types-and-extern-types]: #unsized-types-and-extern-types + +[Extern types][rfc_extern_types] has long been blocked on these types being +neither `Sized` nor `?Sized` ([relevant issue][issue_extern_types_align_size]). + +RFC #1861 defined that `std::mem::size_of_val` and `std::mem::align_of_val` +should not be defined for extern types but not how this should be achieved, and +suggested an initial implementation could panic when called with extern types, +but this is always wrong, and not desirable behavior in keeping with Rust's +values. `size_of_val` returns 0 and `align_of_val` returns 1 for extern types in +the current implementation. Ideally `size_of_val` and `align_of_val` would error +if called with an extern type, but this cannot be expressed in the bounds of +`size_of_val` and `align_of_val` and this remains a blocker for extern types. + +Furthermore, unsized types cannot be members of structs as their alignment is +unknown and this is necessary to calculate field offsets. `extern type`s also +cannot be used in `Box` as `Box` requires size and alignment for both allocation +and deallocation. + +Introducing a hierarchy of `Sized` traits will enable extern types to implement +a trait for unsized types and therefore not meet the bounds of `size_of_val` +and `align_of_val`. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation +Most types in Rust have a size known at compilation time, such as `u32` or +`String`. However, some types in Rust do not have known sizes. + +For example, slices have an unknown length while compiling and are +known as *dynamically-sized types*, their size must be computed at runtime. +There are also types with a statically known size but only at runtime, and +*unsized types* with no size whatsoever. + +Rust uses marker traits to indicate whether a type is sized, when this is known +and if the size needs to be computed. There are four traits related to the size of +a type in Rust: `Sized` , `RuntimeSized`, `DynSized` and `Unsized`. + +Everything which implements `Sized` also implements `RuntimeSized`, and +likewise with `RuntimeSized` with `DynSized`, and `DynSized` with `Unsized`. + +``` +┌─────────────────────────────────────────────────┐ +│ ┌─────────────────────────────────────┐ │ +│ │ ┌────────────────────────┐ │ │ +│ │ │ ┌───────┐ │ │ │ +│ │ │ │ Sized │ RuntimeSized │ DynSized │ Unsized │ +│ │ │ └───────┘ │ │ │ +│ │ └────────────────────────┘ │ │ +│ └─────────────────────────────────────┘ │ +└─────────────────────────────────────────────────┘ +``` + +`Unsized` is implemented by any type which may or may not be sized, which is +to say, every type - from a `u32` which is obviously sized (32 bits) to an +`extern type` (from [rfcs#1861][rfc_extern_types]) which has no known size. + +`DynSized` is a subset of `Unsized`, and excludes those types whose sizes +cannot be computed at runtime. + +Similarly, `RuntimeSized` is a subset of `DynSized`, and excludes those types +whose sizes are not statically known at runtime. Scalable vectors (from +[rfc#3268][rfc_scalable_vectors]) have an unknown size at compilation time but +statically known size at runtime. + +And finally, `Sized` is a subset of `RuntimeSized`, and excludes those types +whose sizes are not statically known at compilation time. + +All type parameters have an implicit bound of `Sized` which will be automatically +removed if a `RuntimeSized`, `DynSized` or `Unsized` bound is present instead. +Prior to the introduction of `RuntimeSized`, `DynSized` and `Unsized`, `Sized`'s +implicit bound could be removed using the `?Sized` syntax, which is now +equivalent to a `DynSized` bound and will be deprecated in the next edition. + +Various parts of Rust depend on knowledge of the size of a type to work, for +example: + +- `std::mem::size_of_val` computes the runtime size of a value, and thus + cannot accept `extern type`s, and this should be prevented by the type + system. +- Rust allows dynamically-sized types to be used as struct fields, but the + alignment of the type must be known, which is not the case for + `extern type`s. +- Allocation and deallocation of an object with `Box` requires knowledge of + its size and alignment, which `extern type`s do not have. +- For a value type to be allocated on the stack, it needs to have statically + known size, which dynamically-sized and unsized types do not have (but + sized and "runtime sized" types do). + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +Introduce three new marker traits, `RuntimeSized`, `DynSized` and `Unsized`, +creating a "hierarchy" of `Sized` traits: + +- `Unsized` + - Types that may not have a knowable size at all. + - `Unsized` will be implemented for: + - `DynSized` types + - if a type's size is computable at runtime then it may or + may not have a size (it does) + - `extern type`s from [rfcs#1861][rfc_extern_types] + - compound types where every element is `Unsized` + - In practice, every type will implement `Unsized`. +- `DynSized` + - Types whose size is computable at runtime. + - `DynSized` is a subtrait of `Unsized`. + - `DynSized` will be implemented for: + - `RuntimeSized` types + - if a type's size is statically known at runtime time then it is + trivially computable at runtime + - slices `[T]` + - string slice `str` + - trait objects `dyn Trait` + - compound types where every element is `DynSized` +- `RuntimeSized` + - Types whose size is statically known at runtime. + - `RuntimeSized` is a subtrait of `DynSized`. + - `RuntimeSized` will be implemented for: + - `Sized` types + - if a type's size is statically known at compilation time then it + is also statically known at runtime + - scalable vectors from [rfcs#3268][rfc_scalable_vectors] + - compound types where every element is `RuntimeSized` +- `Sized` + - Types whose size is statically known at compilation time. + - `Sized` is a subtrait of `RuntimeSized`. + - `Sized` will be implemented for: + - primitives `iN`, `uN`, `fN`, `char`, `bool` + - pointers `*const T`, `*mut T` + - function pointers `fn(T, U) -> V` + - arrays `[T; n]` + - never type `!` + - unit tuple `()` + - closures and generators + - compound types where every field is `Sized` + - anything else which currently implements `Sized` + +Like `Sized`, implementations of these three traits are automatically generated +by the compiler and cannot be implemented manually. + +In the compiler, `?Sized` would be made syntactic sugar for a `DynSized` +bound (and eventually removed in [an upcoming edition][#edition-changes]). +A `DynSized` bound is equivalent to a `?Sized` bound: all values in +Rust today whose types do not implement `Sized` are valid arguments to +`std::mem::size_of_val` and as such have a size which can be computed at +runtime, and therefore will implement `DynSized`. As there are currently no +`extern type`s or other types which would not implement `DynSized`, every type +in Rust today which would satisfy a `?Sized` bound would satisfy a `DynSized` +bound. + +A default implicit bound of `Sized` is added by the compiler to every type +parameter `T` that does not have an explicit `Sized`, `?Sized`, `RuntimeSized`, +`DynSized` or `Unsized` bound. This is somewhat unintuitive as typically adding +a trait bound does not remove the implementation of another trait bound from +being implemented, however it's debatable whether this is more or less confusing +than existing `?Sized` bounds. + +An implicit `DynSized` bound is added to the `Self` type of traits. Like +implicit `Sized` bounds, this is omitted if an explicit `Sized`, `RuntimeSized` +bound is present. + +Types implementing only `Unsized` cannot be used in structs (unless it is +`#[repr(transparent)]`) as the alignment of these types would need to be known +in order to calculate field offsets and this would not be possible. Types +implementing only `DynSized` and `Unsized` could continue to be used in structs, +but only as the last field. + +As `RuntimeSized`, `DynSized` and `Unsized` are not default bounds, there is no +equivalent to `?Sized` for these traits. + +## Edition changes +[edition-changes]: #edition-changes + +In the next edition, writing `?Sized` bounds would no longer be accepted and the +compiler would suggest to users writing `DynSized` bounds instead. Existing `? +Sized` bounds can be trivially rewritten to `DynSized` bounds by `rustup`. + +## Auto traits and backwards compatibility +[auto-traits-and-backwards-compatibility]: #auto-traits-and-backwards-compatibility + +A hierarchy of `Sized` traits sidesteps [the backwards compatibility hazards +which typically scupper attempts to add new traits implemented on every type] +[changing_rules_of_rust]. + +Adding a new auto trait to the bounds of an existing function would typically +be a breaking change, despite all types implementing the new auto trait, in +two cases: + +1. Callers with generic parameters would not have the new bound. For example, + adding a new auto trait (which is not a default bound) as a bound to `std_fn` + would cause `user_fn` to stop compiling as `user_fn`'s `T` would need the bound + added too: + + ```rust +fn user_fn(value: T) { std_fn(value) } +fn std_fn(value: T) { /* .. */ } +//~^ ERROR the trait bound `T: NewAutoTrait` is not satisfied + ``` + + Unlike with an arbitrary new auto trait, the proposed traits are all + subtraits of `Sized` and every generic parameter either has a default bound + of `Sized` or has a `?Sized` bound, which enables this risk of backwards + compatibility to be avoided. + + Relaxing the `Sized` bound of an existing function's generic parameters to + `RuntimeSized`, `DynSized` or `Unsized` would not break any callers, as those + callers' generic parameters must already have a `T: Sized` bound and therefore + would already satisfy the new relaxed bound. Callers may now have a stricter + bound than is necessary, but they likewise can relax their bounds without that + being a breaking change. + + If an existing function had a generic parameter with a `?Sized` bound and + this bound were changed to `DynSized` or relaxed to `Unsized`, then callers' + generic parameters would either have a `T: Sized` or `T: ?Sized` bound: + + - If callers' generic parameters have a `T: Sized` bound then there would be + no breaking change as `T: Sized` implies the changed or relaxed bound. + - If callers' generic parameter have a `T: ?Sized` bound then this is + equivalent to a `T: DynSized` bound, as described earlier. Therefore there would + be no change as `T: DynSized` implies the changed or relaxed bound. + + ```rust +fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized +fn std_fn(value: T) { /* .. */ } + ``` + + If an existing function's generic parameter had a `?Sized` bound and this + bound were changed to `RuntimeSized` then this *would* be a breaking change, but + it is not expected that this change would be applied to any existing functions + in the standard library. + + The proposed traits in this RFC are only a non-breaking change because the + new auto traits are being added as subtraits of `Sized`, adding supertraits of + `Sized` would be a breaking change. + +2. Trait objects passed by callers would not imply the new trait. For example, + adding a new auto trait as a bound to `std_fn` would cause `user_fn` to stop + compiling as its trait object would not automatically implement the new auto + trait: + + ```rust +fn user_fn(value: Box) { std_fn(value) } +fn std_fn(value: &T) { /* ... */} +//~^ ERROR the trait bound `dyn ExistingTrait: NewAutoTrait` is not satisfied in `Box` + ``` + + Like the previous case, due to the proposed traits being subtraits of + `Sized`, and every trait object implementing `Sized`, adding a `RuntimeSized`, + `DynSized`, or `Unsized` bound to any existing generic parameter would be + already satisfied. + +## Changes to the standard library +[changes-to-the-standard-library]: #changes-to-the-standard-library + +With these new traits and having established changes to existing bounds which +can be made while preserving backwards compatibility, the following changes +could be made to the standard library: + +- [`std::mem::size_of_val`][api_size_of_val] and + [`std::mem::align_of_val`][api_align_of_val] + - `T: ?Sized` becomes `T: DynSized` + - As described previously, `?Sized` is equivalent to `DynSized` due to the + existence of `size_of_val`, therefore this change does not break any existing + callers. + - `DynSized` would not be implemented by `extern type`s from [rfcs#1861] + [rfc_extern_types], which would prevent these functions from being invoked on + types which have no known size or alignment. + - `size_of_val` and `align_of_val` are currently `const` unstably and these + types would no longer be able to be made `const` as this would require `T: + Sized`, not `T: DynSized`. +- [`std::clone::Clone`][api_clone] + - `Clone: Sized` becomes `Clone: RuntimeSized` + - `RuntimeSized` is implemented by more types than `Sized` and by all types + which implement `Sized`, therefore any current implementor of `Clone` will not + break. + - `RuntimeSized` will be implemented by scalable vectors from + [rfcs#3268][rfc_scalable_vectors] whereas `Sized` would not have been, and + this is correct as `RuntimeSized` types can still be cloned. As `Copy: Clone`, + this allows scalable vectors to be `Copy`, which is necessary and currently a + blocker for that feature. +- [`std::boxed::Box`][api_box] + - `T: ?Sized` becomes `T: DynSized` + - As before, this is not a breaking change and prevents types only + implementing `Unsized` from being used with `Box`, as these types do not have + the necessary size and alignment for allocation/deallocation. + +As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard +library would need to be reviewed and updated as appropriate. + +# Drawbacks +[drawbacks]: #drawbacks + +This is a fairly large change to the language given how fundamental the `Sized` +trait is, so it could be deemed too confusing to introduce more complexity. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +There are various points of difference to the [prior art](#prior-art) related to +`Sized`, which spans almost the entirety of the design space: + +- In contrast to [rfcs#709][rfc_truly_unsized_types], marker types aren't used + to disable `Sized` because we are able to introduce traits to do so without + backwards compatibility hazards and that feels more appropriate for Rust. +- In contrast to [rfcs#1524][rfc_custom_dst], the `Sized` trait isn't extended + as this wouldn't be sufficient to capture the range in sizedness that this RFC + aims to capture (i.e. for scalable vectors with `RuntimeSized` or `extern type`s + with `Unsized`), even if it theoretically could enable Custom DSTs. +- In contrast to [rfcs#1993][rfc_opaque_data_structs], [rust#44469][pr_dynsized], + [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_and_dynsized] and + [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], + none of the traits proposed in this RFC are default bounds and therefore do not + need to support being relaxed bounds (i.e. no `?RuntimeSized`), which avoids + additional language complexity and backwards compatibility hazards related to + relaxed bounds and associated types. +- In contrast to [rfcs#1524][rfc_custom_dst], [rfc#1993][rfc_opaque_data_structs], + [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts], [Pre-RFC: Custom DSTs][prerfc_custom_dst] + and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], + `DynSized` does not have `size_of_val`/`align_of_val` methods to support custom DSTs, + but like [rfcs#2310][rfc_dynsized_without_dynsized], this RFC prefers keeping + these functions out of `DynSized` and having all of the traits be solely marker + traits. Custom DSTs are still compatible with this proposal using a `Contiguous` + trait as in [rfcs#2594][rfc_custom_dst_electric_boogaloo]. + +All of the trait names proposed in the RFC can be bikeshed and changed, they'll +ultimately need to be decided but aren't the important part of the RFC. + +There are not many alternatives to this RFC to unblock `extern type`s and +scalable vectors: + +- Without this RFC, scalable vectors from [rfcs#3268][rfc_scalable_vectors] + would remain blocked unless special-cased by the compiler to bypass the type + system. +- Extern types from [rfcs#1861][rfc_extern_types] would remain blocked if no + action was taken, unless: + - The language team decided that having `size_of_val` and `align_of_val` + panic was acceptable. + - The language team decided that having `size_of_val` and `align_of_val` + return `0` and `1` respectively was acceptable. + - The language team decided that `extern type`s could not be instantiated + into generics and that this was acceptable. + - The language team decided that having `size_of_val` and `align_of_val` + produce post-monomorphisation errors for `extern type`s was acceptable. +- It would not be possible for this change to be implemented as a library or + macro instead of in the language itself. + +# Prior art +[prior-art]: #prior-art + +There have been many previous proposals and discussions attempting to resolve +the `size_of_val` and `align_of_val` for `extern type`s through modifications to +the `Sized` trait, summarised below: + +- [rfcs#709: truly unsized types][rfc_truly_unsized_types], [mzabaluev][author_mzabaluev], Jan 2015 + - Earliest attempt to opt-out of `Sized`. + - Proposes dividing types which do not implement `Sized` into DSTs and types + of indeterminate size. + - Adding a field with a `std::marker::NotSized` type will make a type + opt-out of `Sized`, preventing the type from being used in all the places where + it needs to be `Sized`. + - Dynamically sized types will "intrinsically" implement `DynamicSize`, + references to these types will use fat pointers. + - Ultimately postponed for post-1.0. +- [rfcs#813: truly unsized types (issue)][issue_truly_unsized_types], [pnkfelix][author_pnkfelix], Feb 2015 + - Tracking issue for postponed [rfcs#709][rfc_truly_unsized_types]. + - Links to an newer version of [rfcs#709][rfc_truly_unsized_types], still + authored by [mzabaluev][author_mzabaluev]. + - Proposes being able to opt-out of `Sized` with a negative impl (a `CStr` + type containing only a `c_char` is the example given of a DST which would + opt-out of `Sized`). + - Also proposes removing `Sized` bound on various `AsPtr`/`AsMutPtr`/ + `FromPtr`/`FromMutPtr` traits as they existed at the time, so that a user might + be able to implement these to preserve the ability to use a thin pointer for + their unsized type when that is possible. + - Ultimately closed after [rfcs#1861][rfc_extern_types] was merged and + intended that [rfcs#2255][issue_more_implicit_bounds] be used to discuss the + complexities of that proposal. +- [rfcs#1524: Custom Dynamically Sized Types][rfc_custom_dst], [strega-nil][author_strega_nil], Mar 2016 + - Successor of [rfcs#709][rfc_truly_unsized_types]/[rfcs#813][issue_truly_unsized_types]. + - Proposes an `unsafe trait !Sized` (which isn't just a negative impl), with + an associated type `Meta` and `size_of_val` method. + - Under this proposal, users would create a "borrowed" version of their + type (e.g. what `[T]` is to `Vec`) which has a zero-sized last field, which + is described in the RFC as "the jumping off point for indexing your block of + memory". + - These types would implement `!Sized`, providing a type for `Meta` + containing any extra information necessary to compute the size of the DST (e.g. + a number of strides) and an implementation of `size_of_val` for the type. + - There would be intrinsics to help make create instances of + these dynamically sized types, namely `make_fat_ptr`, `fat_ptr_meta` and + `size_of_prelude`. +- [rfcs#1861: extern types][rfc_extern_types], [canndrew][author_canndrew], Jan 2017 + - Merged in Jul 2017. + - This RFC mentions the issue with `size_of_val` and `align_of_val` but + suggests that these functions panic in an initial implementation and that + "before this is stabilised, there should be some trait bound or similar on them + that prevents there use statically". Inventing an exact mechanism was intended + to be completed by [rfcs#1524][rfc_custom_dst] or its like. +- [rfcs#1993: Opaque Data structs for FFI][rfc_opaque_data_structs], [mystor][author_mystor], May 2017 + - This RFC was an alternative to the original `extern type`s RFC + ([rfcs#1861][rfc_extern_types]) and introduced the idea of a `DynSized` auto + trait. + - Proposes a `DynSized` trait which was a built-in, unsafe, auto trait, + a supertrait of `Sized`, and a default bound which could be relaxed with + `? DynSized`. + - It would automatically implemented for everything that didn't have an + `Opaque` type in it (this RFC's equivalent of an `extern type`). + - `size_of_val` and `align_of_val` would have their bounds changed to + `DynSized`. + - Trait objects would have a `DynSized` bound by default and the + `DynSized` trait would have `size_of_val` and `align_of_val` member functions. + - Ultimately closed as [rfcs#1861][rfc_extern_types] was entering final + comment period. +- [rust#43467: Tracking issue for RFC 1861][issue_tracking_extern_types], [aturon][author_aturon], Jul 2017 + - Tracking thread created for the implementation of [rfc#1861][rfc_extern_types]. + - In 2018, the language team had consensus against having `size_of_val` + return a sentinel value and adding any trait machinery, like `DynSized`, didn't + seem worth it, preferring to panic or abort. + - This was considering `DynSized` with a relaxed bound. + - Anticipating some form of custom DSTs, there was the possibility + that `size_of_val` could run user code and panic anyway, so making it panic + for `extern type`s wasn't as big an issue. `size_of_val` running in unsafe code + could be a footgun and that caused mild concern. + - See [this comment](https://github.com/rust-lang/rust/issues/43467#issuecomment-377521693) + and [this comment](https://github.com/rust-lang/rust/issues/43467#issuecomment-377665733). + - Conversation became more sporadic following 2018 and most + recent discussion was spurred by the + [Sized, DynSized and Unsized][blog_dynsized_unsized] blog post. + - See [this comment](https://github.com/rust-lang/rust/issues/43467#issuecomment-2073513472) + onwards. + - It's unclear how different language team opinion is since the 2018 + commentary, but posts like above suggest some change. +- [rust#44469: Add a `DynSized` trait][pr_dynsized], [plietar][author_plietar], Sep 2017 + - This pull request intended to implement the `DynSized` trait from + [rfcs#1993][rfc_opaque_data_structs]. + - `DynSized` as implemented is similar to that from + [rfcs#1993][rfc_opaque_data_structs] except it is implemented for every + type with a known size and alignment at runtime, rather than requiring an + `Opaque` type. + - In addition to preventing `extern type`s being used in `size_of_val` and + `align_of_val`, this PR is motivated by wanting to have a mechanism by which + `!DynSized` types can be prevented from being valid in struct tails due to needing + to know the alignment of the tail in order to calculate its offset, and this is + - `DynSized` had to be made a implicit supertrait of all traits in this + implementation - it is presumed this is necessary to avoid unsized types + implementing traits. + - This actually went through FCP and would have been merged if not + eventually closed for inactivity. +- [rust#46108: Add DynSized trait (rebase of #44469)][pr_dynsized_rebase], [mikeyhew][author_mikeyhew], Nov 2017 + - This pull request is a resurrection of [rust#44469][pr_dynsized]. + - Concerns were raised about the complexity of adding another `?Trait` to + the language, and suggested that having `size_of_val` panic was sufficient (the + current implementation does not panic and returns zero instead, which is also + deemed undesirable). + - It was argued that `?Trait`s are powerful and should be made more + ergonomic rather than avoided. + - [kennytm][author_kennytm] left a useful comment summarising [which + standard library bounds would benefit from relaxation to a `DynSized` bound] + (https://github.com/rust-lang/rust/pull/46108#issuecomment-353672604). + - Ultimately this was closed [after a language team meeting](https://github.com/rust-lang/rust/pull/46108#issuecomment-360903211) + deciding that `?DynSized` was ultimately too complex and couldn't be + justified by support for a relatively niche feature like `extern type`. +- [rfcs#2255: More implicit bounds (?Sized, ?DynSized, ?Move)][issue_more_implicit_bounds], [kennytm][author_kennytm], Dec 2017 + - Issue created following [rust#46108][pr_dynsized_rebase] to discuss the + complexities surrounding adding new traits which would benefit from relaxed + bounds (`?Trait` syntax). + - There have been various attempts to introduce new auto traits with + implicit bounds, such as `DynSized`, `Move`, `Leak`, etc. Often rejected due to + the ergonomic cost of relaxed bounds. + - `?Trait` being a negative feature confuses users. + - Downstream crates need to re-evaluate every API to determine if adding + `?Trait` makes sense, for each `?Trait` added. + - `?Trait` isn't actually backwards compatible like everyone thought due + to interactions with associated types. + - This thread was largely motivated by the `Move` trait and that was + replaced by the `Pin` type, but there was an emerging consensus that `DynSized` + may be more feasible due to its relationship with `Sized`. +- [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts], [mikeyhew][author_mikeyhew], Jan 2018 + - This eRFC was written as a successor to [rfcs#1524][rfc_custom_dst]. + - It proposes `DynSized` trait and a bunch of others. `DynSized` is a + supertrait of `Sized` (indirectly) and contains a `size_of_val` method. This + proposal is the first to remove `Sized` bounds if another sized trait (e.g. + `DynSized`) has an explicit bound. + - This enables deprecation of `?Sized` like this RFC proposes. + - A `Thin` type to allow thin pointers to DSTs is also proposed in + this pre-eRFC - it is a different `Thin` from the currently unstable + `core::ptr::Thin` and it's out-of-scope for this RFC to include a similar type + and since accepted [rfcs#2580][rfc_pointer_metadata_vtable] overlaps. + - This pre-eRFC may be the origin of the idea for a family of `Sized` + traits, later cited in [Sized, DynSized, and Unsized][blog_dynsized_unsized]. + - [rfcs#2510][rfc_pointer_metadata_vtable] was later submitted which was a + subset of this proposal (but none of the `DynSized` parts). + - This eRFC ultimately fizzled out and didn't seem to result in a proper RFC + being submitted. +- [rfcs#2310: DynSized without ?DynSized][rfc_dynsized_without_dynsized], [kennytm][author_kennytm], Jan 2018 + - This RFC proposed an alternative version of `DynSized` from + [rfcs#1993][rfc_opaque_data_structs]/[rust#44469][pr_dynsized] but without + being an implicit bound and being able to be a relaxed bound (i.e. no + `?DynSized`). + - Adding new implicit bounds which can be relaxed has backwards + compatibility hazards, see [rfcs#2255][issue_more_implicit_bounds]. + - The proposed `DynSized` trait in [rfcs#2310][rfc_dynsized_without_dynsized] + is really quite similar to the trait proposed by this RFC except: + - It includes an `#[assume_dyn_sized]` attribute to be added to + `T: ?Sized` bounds instead of replacing them with `T: DynSized`, which + would warn instead of error when a non-`DynSized` implementing type is + substituted into `T`. + - This is to avoid a backwards compatibility break for uses of + `size_of_val` and `align_of_val` with `extern type`s, but it is + unclear why this is necessary given that `extern type`s are + unstable. + - It does not include `RuntimeSized` or `Unsized`. + - Adding an explicit bound for `DynSized` does not remove the implicit + bound for `Sized`. +- [rust#49708: `extern type` cannot support `size_of_val` and `align_of_val`][issue_extern_types_align_size], [joshtriplett][author_joshtriplett], Apr 2018 + - Primary issue for the `size_of_val`/`align_of_val` `extern type`s + blocker, following no resolution from either of [rfcs#1524][rfc_custom_dst] and + [rust#44469][pr_dynsized] or their successors. + - This issue largely just re-hashes the arguments made in other threads + summarised here. +- [Pre-RFC: Custom DSTs][prerfc_custom_dst], [ubsan][author_ubsan], Nov 2018 + - This eRFC was written as a successor to [rfcs#1524][rfc_custom_dst]. + - Proposes addition of a `DynamicallySized` trait with a `Metadata` + associated type and `size_of_val` and `align_of_val` member functions. + - It has an automatic implementation for all `Sized` types, where + `Metadata = ()` and `size_of_val` and `align_of_val` just call `size_of` and + `align_of`. + - It can be manually implemented for DSTs and if it is, the type will + not implement `Sized`. + - Due to `DynamicallySized` not being a supertrait of `Sized`, this proposal + had no way of modifying the bounds of `size_of_val` and `align_of_val` without + it being a breaking change (and so did not propose doing so). + - This eRFC ultimately fizzled out and didn't seem to result in a proper RFC + being submitted. +- [rfcs#2594: Custom DSTs][rfc_custom_dst_electric_boogaloo], [strega-nil][author_strega_nil], Nov 2018 + - This eRFC was written as a successor to [rfcs#1524][rfc_custom_dst]. + - This is more clearly an direct evolution of [rfcs#1524][rfc_custom_dst] + than other successors were, unsurprisingly given the same author. + - Proposes a `Pointee` trait with `Metadata` associated type and a + `Contiguous` supertrait of `Pointee` with `size_of_val` and `align_of_val` + members. + - `Sized` is a subtrait of `Pointee` (as sized types + have thin pointers). `Sized` also implements `Contiguous` calling `size_of` and + `align_of` for each of the member functions. + - Dynamically sized types can implement `Pointee` manually and provide + a `Metadata` associated type, and then `Contiguous` to implement `size_of_val` + and `align_of_val`. + - Intrinsics are added for constructing a pointer to a dynamically + sized type from its metadata and value, and for accessing the metadata of a + dynamically sized type. + - `extern type`s do not implement `Contiguous` but do implement `Pointee`. + - `Contiguous` is a default bound and so has a relaxed form `?Contiguous`. + - There's plenty of overlap here with [rfcs#2580][rfc_pointer_metadata_vtable] + and its `Pointee` trait - the accepted [rfcs#2580][rfc_pointer_metadata_vtable] + does not make `Sized` a subtrait of `Pointee` or have a `Contiguous` trait but + the `Pointee` trait is more or less compatible. + - Discussed in a [November 4th 2020 design meeting](https://www.youtube.com/watch?v=wYmJK62SSOM&list=PL85XCvVPmGQg-gYy7R6a_Y91oQLdsbSpa&index=63) + ([pre-meeting notes](https://hackmd.io/1Fq9TcAQRWa4_weWTe9adA) and + [post-meeting notes](https://github.com/rust-lang/lang-team/blob/master/design-meeting-minutes/2020-11-04-RFC-2580-and-custom-dst.md)). + - Meeting was mostly around [rfcs#2580][rfc_pointer_metadata_vtable] but + mentioned the state of Custom DSTs. + - Mentioned briefly in a [language team triage meeting](https://www.youtube.com/watch?v=NzURKQouuEU&t=3292s) + in March 2021 and postponed until [rfcs#2510][rfc_pointer_metadata_vtable] + was implemented. +- [Design Meeting][design_meeting], Language Team, Jan 2020 + - Custom DSTs and `DynSized` are mentioned but there aren't any implications + for this RFC. +- [rfcs#2984: introduce `Pointee` and `DynSized`][rfc_pointee_dynsized], [nox][author_nox], Sep 2020 + - This RFC aims to land some traits in isolation so as to enable progress on + other RFCs. + - Proposes a `Pointee` trait with associated type `Meta` (very similar to + accepted [rfcs#2580][rfc_pointer_metadata_vtable]) and a `DynSized` trait which + is a supertrait of it. `Sized` is made a supertrait of `DynSized`. + Neither new trait can be implemented by hand. + - It's implied that `DynSized` is implemented for all dynamically sized + types, but it isn't clear. + - Despite being relatively brief, this RFC has lots of comments. + - The author argues that `?DynSized` is okay and disagrees with + previous concerns about complexity and that all existing bounds would need to be + reconsidered in light of `?DynSized`. + - In response, it is repeatedly argued that there is a mild + preference for making `size_of_val` and `align_of_val` panic instead of adding + `?Trait` bounds and that having the ability to do `Pointee` type + bounds is sufficient. +- [Exotically sized types (`DynSized` and `extern type`)][design_notes_dynsized_constraints], Language Team, Jun 2022 + - Despite being published in Jun 2022, these are reportedly notes from a + previous Jan 2020 meeting, but not the one above. + - Explores constraints `Arc`/`Rc` and `Mutex` imply on `DynSized` bounds. + - `MetaSized` is first mentioned in these meeting notes, as when the size/ + alignment can be known from pointer metadata. +- [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], [CAD97][author_cad97], May 2022 + - This RFC proposes a forever-unstable default-bound unsafe trait `DynSized` + with `size_of_val_raw` and `align_of_val_raw`, implemented for everything other + than `extern type`s. Users can implement `DynSized` for their own types. This + proposal doesn't say whether `DynSized` is a default bound but does mention a + relaxed form of the trait `?DynSized`. +- [rfcs#3319: Aligned][rfc_aligned], [Jules-Bertholet][author_jules_bertholet], Sep 2022 + - This RFC aims to separate the alignment of a type from the size of the + type with an `Aligned` trait. + - Automatically implemented for all types with an alignment (includes + all `Sized` types). + - `Aligned` is a supertrait of `Sized`. +- [rfcs#3396: Extern types v2][rfc_extern_types_v2], [Skepfyr][author_skepfyr], Feb 2023 + - Proposes a `MetaSized` trait for types whose size and alignment can + be determined solely from pointer metadata without having to dereference the + pointer or inspect the pointer's address. + - Under this proposal, `[T]` is `MetaSized` as the pointer metadata + knows the size, rather than `DynSized`. + - `MetaSized` is automatically implemented for all types except extern + types. + - `MetaSized` types can be the last field of a struct as the offset can + be determined from the pointer metadata alone. + - `Sized` is not a supertrait or subtrait of `MetaSized`. + - This may make the proposal subject to the backwards + incompatibilities described in + [Changes to the Standard Library](#changes-to-the-standard-library). + - `size_of_val`'s bound would be changed to `T: ?Sized + MetaSized`. + - Attempts to sidestep backwards compatibility issues with introducing a + default bound via changing what `?Sized` means across an edition boundary. + - This [may be backwards incompatible](https://github.com/rust-lang/rfcs/pull/3396#issuecomment-1728509626). + - Discussed in [a language team design meeting](https://hackmd.io/TSXpOX4iS3qqDdVD00z7tw?view). +- [rfcs#3536: Trait for `!Sized` thin pointers][rfc_not_sized_thin_pointers], [jmillikin][author_jmillikin], Nov 2023 + - Introduces unsafe trait `DynSized` with a `size_of_val` method. + - It can be implemented on `!Sized` types. + - It is an error to implement it on `Sized` types. + - References to types that implement `DynSized` do not need to store the + size in pointer metadata. Types implementing `DynSized` without other pointer + metadata are thin pointers. + - This proposal has no solution for `extern type` limitations, its sole aim + is to enable more pointers to be thin pointers. +- [Sized, DynSized, and Unsized][blog_dynsized_unsized], [Niko Matsakis][author_nikomatsakis], Apr 2024 + - This proposes a hierarchy of `Sized`, `DynSized` and `Unsized` traits + like in this RFC and proposes deprecating `T: ?Sized` in place of `T: Unsized` + and sometimes `T: DynSized`. Adding a bound for any of `DynSized` or `Unsized` + removes the default `Sized` bound. + - As described below it is the closest inspiration for this RFC. + +There are some even older RFCs that have tangential relevance that are listed +below but not summarized: + +- [rfcs#5: virtual structs][rfc_virtual_structs], [nrc][author_nrc], Mar 2014 +- [rfcs#9: RFC for "fat objects" for DSTs][rfc_fat_objects], [MicahChalmer][author_micahchalmer], Mar 2014 +- [pre-RFC: unsized types][rfc_unsized_types], [japaric][author_japaric], Mar 2016 + +There haven't been any particular proposals which have included an equivalent +of the `RuntimeSized` trait, as the scalable vector types proposal in [RFC 3268] +[rfc_scalable_vectors] is relatively newer and less well known: + +- [rfcs#3268: Add scalable representation to allow support for scalable vectors][rfc_scalable_vectors], [JamieCunliffe][author_jamiecunliffe], May 2022 + - Proposes temporarily special-casing scalable vector types to be able to + implement `Copy` without implementing `Sized` and allows function return values + to be `Copy` or `Sized` (not just `Sized`). + - Neither of these changes would be necessary with this RFC, scalable + vectors would just implement `RuntimeSized` and function return values would + just need to implement `RuntimeSized`, not `Sized`. + +To summarise the above exhaustive listing of prior art: + +- No previous works have proposed a `RuntimeSized` trait or a `Unsized` trait + (with the exception of [Sized, DynSized, and Unsized][blog_dynsized_unsized] for + `Unsized`), only `DynSized`. +- One proposal proposed adding a marker type that as a field would result in the + containing type no longer implementing `Sized`. +- Often proposals focused at Custom DSTs preferred to combine the + escape-the-sized-hierarchy part with the Custom DST machinery. + - e.g. `DynSized` trait with `Metadata` associated types and `size_of_val` + and `align_of_val` methods, or a `!Sized` pseudo-trait that you could implement. + - Given the acceptance of [rfcs#2580][rfc_pointer_metadata_vtable], Rust + doesn't seem to be trending in this direction, as the `Metadata` part of this is + now part of a separate `Pointee` trait. +- Most early `DynSized` trait proposals (independent or as part of Custom DSTs) + would make `DynSized` a default bound mirroring `Sized`, and consequently had a + relaxed form `?DynSized`. + - Later proposals were more aware of the language team's resistance towards + adding new relaxed bounds and tried to avoid this. +- Backwards compatibility concerns were the overriding reason for the rejection + of previous `DynSized` proposals. + - These can be sidestepped by avoiding having a relaxed form and by relying + on being a supertrait of `Sized`. + +The [Rationale and Alternatives](#rationale-and-alternatives) section provides +rationale for some of the decisions made in this RFC and references the prior +art above when those proposals made different decisions. + +No previous proposal captures the specific part of the design space that this +proposal attempts to, but these proposals are the closest matches for parts of +this proposal: + +- [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts] was the only other proposal + removing `Sized` bounds when a bound for another sized trait (only `DynSized` + in that pre-eRFC's case) was present, which makes reasoning simpler by avoiding + relaxed bounds. + - However, this proposal had `size_of_val` methods its `DynSized` and + proposed a bunch of other things necessary for Custom DSTs. +- [rfcs#2310: DynSized without ?DynSized][rfc_dynsized_without_dynsized] was + proposed at a similar time and was similarly focused only on making `Sized` more + flexible, but had a bunch of machinery for avoiding backwards incompatibility + that this RFC believes is unnecessary. Like this proposal, it avoided making + `DynSized` a default bound and avoided having a relaxed form of it. + - However, this proposal didn't suggest removing default `Sized` bounds in + the presence of other size trait bounds. +- [Sized, DynSized, and Unsized][blog_dynsized_unsized] is very similar and a + major inspiration for this proposal. It has everything this proposal has except + for `RuntimeSized` and all the additional context an RFC needs. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +- [rust#21974][issue_regions_too_simplistic] has been linked in previous + `DynSized` proposals as a reason why `DynSized` (as then proposed) must be + an implicit supertrait of all traits, but it isn't obvious why this issue is + relevant. + - It seems like `DynSized` being a supertrait of all traits is necessary so + that `extern type`s do not implement any traits anyway. +- `extern type` `OpaqueListContents` is used as the last field of a struct in + rustc, which currently works because it has a presumed alignment of one. This + would be prohibited by the RFC as written, but relaxing this is listed as a + future possibility. + - Adding limitations to the use of `Unsized` types in structs could be left + as a follow-up as it is more related to `extern type` than extending `Sized` + anyway. Implementation of this RFC would not create any `Unsized` types. +- How would this interact with proposals to split alignment and sizedness into + separate traits? +- Some prior art had different rules for automatically implementing `DynSized` + on structs/enums vs on unions - are those necessary for these traits? + +# Future possibilities +[future-possibilities]: #future-possibilities + +- Additional size traits could be added as supertraits of `Sized` if there are + other delineations in sized-ness that make sense to be drawn. + - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2], something like + `Sized: MetaSized`, `MetaSized: RuntimeSized`, `RuntimeSized: DynSized` and + `DynSized: Unsized`. +- The requirement that users cannot implement any of these traits could be + relaxed in future to support custom DSTs or any other proposed feature which + required it. +- Relax prohibition of `Unsized` fields in some limited contexts, for example if + it is the final field and the offset of it is never computed. +- In addition to the default bound changes described above, the default + `T: Sized` bound could be omitted if any other bound `T: Trait` had an explicit + supertrait of `RuntimeSized`, `DynSized` or `Unsized`. This would allow + boilerplate `?Sized` bounds to be removed. + - Credit to [Sized, DynSized, and Unsized][blog_dynsized_unsized] for this idea. +- This proposal is compatible with adding Custom DSTs in future. + - Leveraging the already accepted [rfcs#2580][rfc_pointer_metadata_vtable] + and taking an approach similar to [rfcs#2594][rfc_custom_dst_electric_boogaloo] + with its `Contiguous` trait to provide somewhere for `size_of_val` and + `align_of_val` to be implemented for the Custom DST. + - Custom DSTs would need to implement `DynSized` and not implement + `Sized` to be compatible with the traits introduced in this proposal, and maybe + you'd want to add `Pointee` as a supertrait of `DynSized` with some non-`()` + metadata type, or something along those lines. + +[api_align_of_val]: https://doc.rust-lang.org/std/mem/fn.align_of_val.html +[api_box]: https://doc.rust-lang.org/std/boxed/struct.Box.html +[api_clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html +[api_size_of_val]: https://doc.rust-lang.org/std/mem/fn.size_of_val.html +[author_aturon]: https://github.com/aturon +[author_cad97]: https://github.com/CAD97 +[author_canndrew]: https://github.com/canndrew +[author_jamiecunliffe]: https://github.com/JamieCunliffe +[author_japaric]: https://github.com/japaric +[author_jmillikin]: https://github.com/jmillikin +[author_joshtriplett]: https://github.com/joshtriplett +[author_jules_bertholet]: https://github.com/Jules-Bertholet +[author_kennytm]: https://github.com/kennytm +[author_micahchalmer]: https://github.com/MicahChalmer +[author_mikeyhew]: https://github.com/mikeyhew +[author_mystor]: https://github.com/mystor +[author_mzabaluev]: https://github.com/mzabaluev +[author_nikomatsakis]: https://github.com/nikomatsakis +[author_nox]: https://github.com/nox +[author_nrc]: https://github.com/nrc +[author_plietar]: https://github.com/plietar +[author_pnkfelix]: https://github.com/pnkfelix +[author_skepfyr]: https://github.com/Skepfyr +[author_strega_nil]: https://github.com/strega-nil +[author_ubsan]: https://github.com/ubsan +[blog_dynsized_unsized]: https://smallcultfollowing.com/babysteps/blog/2024/04/23/dynsized-unsized/ +[changing_rules_of_rust]: https://without.boats/blog/changing-the-rules-of-rust/ +[design_meeting]: https://hackmd.io/7r3_is6uTz-163fsOV8Vfg +[design_notes_dynsized_constraints]: https://github.com/rust-lang/lang-team/blob/master/src/design_notes/dynsized_constraints.md +[erfc_minimal_custom_dsts_via_extern_type]: https://internals.rust-lang.org/t/erfc-minimal-custom-dsts-via-extern-type-dynsized/16591?u=cad97 +[issue_extern_types_align_size]: https://github.com/rust-lang/rust/issues/49708 +[issue_more_implicit_bounds]: https://github.com/rust-lang/rfcs/issues/2255 +[issue_regions_too_simplistic]: https://github.com/rust-lang/rust/issues/21974#issuecomment-331886186 +[issue_tracking_extern_types]: https://github.com/rust-lang/rust/issues/43467 +[issue_truly_unsized_types]: https://github.com/rust-lang/rfcs/issues/813 +[pr_dynsized]: https://github.com/rust-lang/rust/pull/44469 +[pr_dynsized_rebase]: https://github.com/rust-lang/rust/pull/46108 +[pre_erfc_fix_dsts]: https://internals.rust-lang.org/t/pre-erfc-lets-fix-dsts/6663 +[prerfc_custom_dst]: https://internals.rust-lang.org/t/pre-rfc-custom-dsts/8777 +[rfc_aligned]: https://github.com/rust-lang/rfcs/pull/3319 +[rfc_custom_dst_electric_boogaloo]: https://github.com/rust-lang/rfcs/pull/2594 +[rfc_custom_dst]: https://github.com/rust-lang/rfcs/pull/1524 +[rfc_dynsized_without_dynsized]: https://github.com/rust-lang/rfcs/pull/2310 +[rfc_extern_types]: https://rust-lang.github.io/rfcs/1861-extern-types.html +[rfc_extern_types_v2]: https://github.com/rust-lang/rfcs/pull/3396 +[rfc_fat_objects]: https://github.com/rust-lang/rfcs/pull/9 +[rfc_not_sized_thin_pointers]: https://github.com/rust-lang/rfcs/pull/3536 +[rfc_opaque_data_structs]: https://github.com/rust-lang/rfcs/pull/1993 +[rfc_pointee_dynsized]: https://github.com/rust-lang/rfcs/pull/2984 +[rfc_pointer_metadata_vtable]: https://github.com/rust-lang/rfcs/pull/2580 +[rfc_scalable_vectors]: https://github.com/rust-lang/rfcs/pull/3268 +[rfc_simd]: https://rust-lang.github.io/rfcs/1199-simd-infrastructure.html +[rfc_truly_unsized_types]: https://github.com/rust-lang/rfcs/pull/709 +[rfc_unsized_types]: https://github.com/japaric/rfcs/blob/unsized2/text/0000-unsized-types.md +[rfc_virtual_structs]: https://github.com/rust-lang/rfcs/pull/5 From dedc07803a8dea2c3fee46e8e9d249e731ac6742 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 14 Oct 2024 10:58:51 +0100 Subject: [PATCH 02/45] updates from EuroRust discussions --- text/0000-sized-hierarchy.md | 112 +++++++++++++++++++++++++++++------ 1 file changed, 94 insertions(+), 18 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 5f6b95d7288..e42751c1bfb 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -2,6 +2,7 @@ - Start Date: 2024-09-30 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + # Summary [summary]: #summary @@ -89,6 +90,24 @@ Introducing a hierarchy of `Sized` traits will enable extern types to implement a trait for unsized types and therefore not meet the bounds of `size_of_val` and `align_of_val`. +# Terminology +[terminology]: #terminology + +In the Rust community, "unsized" and "dynamically sized" are often used +interchangeably to describe any type that does not implement `Sized`. This is +unsurprising as any type which does not implement `Sized` is necessarily +"unsized" and the only types this description captures are those which are +dynamically sized. + +In this RFC, a distinction is made between "unsized" and "dynamically sized" +types. Unsized types is used to refer only to those which have no known +size/alignment, such as those described by [the extern types +RFC][rfc_extern_types]. Dynamically-sized types describes those types whose size +cannot be known statically at compilation time and must be computed at runtime. + +Within this RFC, no terminology is introduced to describe all types which do not +implement `Sized` in the same sense as "unsized" is colloquially used. + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation Most types in Rust have a size known at compilation time, such as `u32` or @@ -236,6 +255,15 @@ but only as the last field. As `RuntimeSized`, `DynSized` and `Unsized` are not default bounds, there is no equivalent to `?Sized` for these traits. +There is a potential performance impact within the trait system to adding +supertraits to `Sized`, as implementation of these supertraits will need to be +proven whenever a `Sized` obligation is being proven (and this happens very +frequently, being a default bound). It may be necessary to implement an +optimisation whereby `Sized`'s supertraits are assumed to be implemented and +checking them is skipped - this should be sound as all of these traits are +implemented by the compiler and therefore this property can be guaranteed by +the compiler implementation. + ## Edition changes [edition-changes]: #edition-changes @@ -247,8 +275,8 @@ Sized` bounds can be trivially rewritten to `DynSized` bounds by `rustup`. [auto-traits-and-backwards-compatibility]: #auto-traits-and-backwards-compatibility A hierarchy of `Sized` traits sidesteps [the backwards compatibility hazards -which typically scupper attempts to add new traits implemented on every type] -[changing_rules_of_rust]. +which typically scupper attempts to add new traits implemented on every +type][changing_rules_of_rust]. Adding a new auto trait to the bounds of an existing function would typically be a breaking change, despite all types implementing the new auto trait, in @@ -287,36 +315,50 @@ fn std_fn(value: T) { /* .. */ } equivalent to a `T: DynSized` bound, as described earlier. Therefore there would be no change as `T: DynSized` implies the changed or relaxed bound. - ```rust -fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized -fn std_fn(value: T) { /* .. */ } - ``` + ```rust + fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized + fn std_fn(value: T) { /* .. */ } + ``` - If an existing function's generic parameter had a `?Sized` bound and this - bound were changed to `RuntimeSized` then this *would* be a breaking change, but - it is not expected that this change would be applied to any existing functions - in the standard library. + If an existing function's generic parameter had a `?Sized` bound and this + bound were changed to `RuntimeSized` then this *would* be a breaking change, but + it is not expected that this change would be applied to any existing functions + in the standard library. - The proposed traits in this RFC are only a non-breaking change because the - new auto traits are being added as subtraits of `Sized`, adding supertraits of - `Sized` would be a breaking change. + The proposed traits in this RFC are only a non-breaking change because the + new auto traits are being added as subtraits of `Sized`, adding supertraits of + `Sized` would be a breaking change. 2. Trait objects passed by callers would not imply the new trait. For example, adding a new auto trait as a bound to `std_fn` would cause `user_fn` to stop compiling as its trait object would not automatically implement the new auto trait: - ```rust -fn user_fn(value: Box) { std_fn(value) } -fn std_fn(value: &T) { /* ... */} -//~^ ERROR the trait bound `dyn ExistingTrait: NewAutoTrait` is not satisfied in `Box` - ``` + ```rust + fn user_fn(value: Box) { std_fn(value) } + fn std_fn(value: &T) { /* ... */} + //~^ ERROR the trait bound `dyn ExistingTrait: NewAutoTrait` is not satisfied in `Box` + ``` Like the previous case, due to the proposed traits being subtraits of `Sized`, and every trait object implementing `Sized`, adding a `RuntimeSized`, `DynSized`, or `Unsized` bound to any existing generic parameter would be already satisfied. +Additionally, it is not expected that this RFC's additions would result in much +churn within the ecosystem. All bounds in the standard library should be re-evaluated +during the implementation of this RFC, but bounds in third-party crates need not be: + +Up-to-`RuntimeSized`-implementing types will primarily be used for localised +performance optimisation, and `Unsized`-only-implementing types will primarily be +used for localised FFI, neither is expected to be so pervasive throughout Rust +software to the extent that all existing `Sized` or `?Sized` bounds would need to +be immediately reconsidered in light of their addition. If a user of a +up-to-`RuntimeSized`-implementing type or a `Unsized`-only-implementing type did +encounter a bound that needed to be relaxed, this could be changed in a patch to +the relevant crate without breaking backwards compatibility as-and-when such bounds +are discovered. + ## Changes to the standard library [changes-to-the-standard-library]: #changes-to-the-standard-library @@ -390,9 +432,43 @@ There are various points of difference to the [prior art](#prior-art) related to traits. Custom DSTs are still compatible with this proposal using a `Contiguous` trait as in [rfcs#2594][rfc_custom_dst_electric_boogaloo]. +## Bikeshedding All of the trait names proposed in the RFC can be bikeshed and changed, they'll ultimately need to be decided but aren't the important part of the RFC. +## Why have `Unsized`? +It may seem that the `Unsized` trait is unnecessary as this is equivalent to the +absense of any bounds whatsoever, but having an `Unsized` trait is necessary to +enable the meaning of `?Sized` to be re-defined to be equivalent to `DynSized` +and avoid complicated behaviour change over an edition. + +Without `Unsized`, if a user wanted to remove all sizedness bounds from a generic +parameter then they would have two options: + +1. Introduce new relaxed bounds (i.e. `?DynSized`), which has been found + unacceptable in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] + summarizes these discussions) +2. Maintain `?Sized`'s existing meaning of removing the implicit `Sized` bound + - This could be maintained with or without having the existence of bounds + for `RuntimeSized` and `DynSized` remove the implicit `Sized` bound. + +The latter is the only viable option, but this would complicate changing +`size_of_val`'s existing `?Sized` bound. + +`?Sized` can be redefined to be equivalent to `DynSized` in all editions +and the syntax can be removed in a future edition only because adding an +`Unsized` bound is equivalent to removing the `Sized` bound and imposing +no constraints on the sizedness of a parameter. + +Without `Unsized`, a complicated migration would be necessary to change +all current uses of `?Sized` to `DynSized` (as these are equivalent) and +then future uses of `?Sized` would now accept more types than `?Sized` +previously did (they would now accept the `extern type`s). + +In [rfcs#3396][rfc_extern_types_v2], `MetaSized` was introduced and used a +similar mechanism over an edition to redefine `?Sized`. + +## Alternatives to this RFC There are not many alternatives to this RFC to unblock `extern type`s and scalable vectors: From 2eaaddbc36371c69b956b094d697ead45dcc3890 Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 16 Oct 2024 16:20:09 +0100 Subject: [PATCH 03/45] updates post niko-discussion --- text/0000-sized-hierarchy.md | 172 ++++++++++++++++++++++------------- 1 file changed, 110 insertions(+), 62 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index e42751c1bfb..bf93ebd7b19 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -2,7 +2,6 @@ - Start Date: 2024-09-30 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) - # Summary [summary]: #summary @@ -120,28 +119,33 @@ There are also types with a statically known size but only at runtime, and Rust uses marker traits to indicate whether a type is sized, when this is known and if the size needs to be computed. There are four traits related to the size of -a type in Rust: `Sized` , `RuntimeSized`, `DynSized` and `Unsized`. +a type in Rust: `Sized` , `RuntimeSized`, `DynSized` and `Pointee`. + +*There is already an unstable trait named `Pointee`, this isn't intended to replace +that trait and either trait may need to be renamed so as not to overlap, but +`Pointee` made sense as a placeholder name for this.* Everything which implements `Sized` also implements `RuntimeSized`, and -likewise with `RuntimeSized` with `DynSized`, and `DynSized` with `Unsized`. +likewise with `RuntimeSized` with `DynSized`, and `DynSized` with `Pointee`. ``` ┌─────────────────────────────────────────────────┐ │ ┌─────────────────────────────────────┐ │ │ │ ┌────────────────────────┐ │ │ │ │ │ ┌───────┐ │ │ │ -│ │ │ │ Sized │ RuntimeSized │ DynSized │ Unsized │ +│ │ │ │ Sized │ RuntimeSized │ DynSized │ Pointee │ │ │ │ └───────┘ │ │ │ │ │ └────────────────────────┘ │ │ │ └─────────────────────────────────────┘ │ └─────────────────────────────────────────────────┘ ``` -`Unsized` is implemented by any type which may or may not be sized, which is +`Pointee` is implemented by any type which may or may not be sized (these +types can always be used from behind a pointer, hence the name), which is to say, every type - from a `u32` which is obviously sized (32 bits) to an `extern type` (from [rfcs#1861][rfc_extern_types]) which has no known size. -`DynSized` is a subset of `Unsized`, and excludes those types whose sizes +`DynSized` is a subset of `Pointee`, and excludes those types whose sizes cannot be computed at runtime. Similarly, `RuntimeSized` is a subset of `DynSized`, and excludes those types @@ -153,8 +157,8 @@ And finally, `Sized` is a subset of `RuntimeSized`, and excludes those types whose sizes are not statically known at compilation time. All type parameters have an implicit bound of `Sized` which will be automatically -removed if a `RuntimeSized`, `DynSized` or `Unsized` bound is present instead. -Prior to the introduction of `RuntimeSized`, `DynSized` and `Unsized`, `Sized`'s +removed if a `RuntimeSized`, `DynSized` or `Pointee` bound is present instead. +Prior to the introduction of `RuntimeSized`, `DynSized` and `Pointee`, `Sized`'s implicit bound could be removed using the `?Sized` syntax, which is now equivalent to a `DynSized` bound and will be deprecated in the next edition. @@ -176,21 +180,22 @@ example: # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Introduce three new marker traits, `RuntimeSized`, `DynSized` and `Unsized`, +Introduce three new marker traits, `RuntimeSized`, `DynSized` and `Pointee`, creating a "hierarchy" of `Sized` traits: -- `Unsized` +- `Pointee` - Types that may not have a knowable size at all. - - `Unsized` will be implemented for: + - i.e. Types which can be used from behind a pointer. + - `Pointee` will be implemented for: - `DynSized` types - if a type's size is computable at runtime then it may or may not have a size (it does) - `extern type`s from [rfcs#1861][rfc_extern_types] - - compound types where every element is `Unsized` - - In practice, every type will implement `Unsized`. + - compound types where every element is `Pointee` + - In practice, every type will implement `Pointee`. - `DynSized` - Types whose size is computable at runtime. - - `DynSized` is a subtrait of `Unsized`. + - `DynSized` is a subtrait of `Pointee`. - `DynSized` will be implemented for: - `RuntimeSized` types - if a type's size is statically known at runtime time then it is @@ -226,18 +231,17 @@ Like `Sized`, implementations of these three traits are automatically generated by the compiler and cannot be implemented manually. In the compiler, `?Sized` would be made syntactic sugar for a `DynSized` -bound (and eventually removed in [an upcoming edition][#edition-changes]). -A `DynSized` bound is equivalent to a `?Sized` bound: all values in -Rust today whose types do not implement `Sized` are valid arguments to -`std::mem::size_of_val` and as such have a size which can be computed at -runtime, and therefore will implement `DynSized`. As there are currently no -`extern type`s or other types which would not implement `DynSized`, every type -in Rust today which would satisfy a `?Sized` bound would satisfy a `DynSized` -bound. +bound (and eventually removed in an upcoming edition). A `DynSized` bound is +equivalent to a `?Sized` bound: all values in Rust today whose types do not +implement `Sized` are valid arguments to `std::mem::size_of_val` and as such +have a size which can be computed at runtime, and therefore will implement +`DynSized`. As there are currently no `extern type`s or other types which +would not implement `DynSized`, every type in Rust today which would satisfy +a `?Sized` bound would satisfy a `DynSized` bound. A default implicit bound of `Sized` is added by the compiler to every type parameter `T` that does not have an explicit `Sized`, `?Sized`, `RuntimeSized`, -`DynSized` or `Unsized` bound. This is somewhat unintuitive as typically adding +`DynSized` or `Pointee` bound. This is somewhat unintuitive as typically adding a trait bound does not remove the implementation of another trait bound from being implemented, however it's debatable whether this is more or less confusing than existing `?Sized` bounds. @@ -246,13 +250,13 @@ An implicit `DynSized` bound is added to the `Self` type of traits. Like implicit `Sized` bounds, this is omitted if an explicit `Sized`, `RuntimeSized` bound is present. -Types implementing only `Unsized` cannot be used in structs (unless it is -`#[repr(transparent)]`) as the alignment of these types would need to be known -in order to calculate field offsets and this would not be possible. Types -implementing only `DynSized` and `Unsized` could continue to be used in structs, -but only as the last field. +Types implementing only `Pointee` cannot be used in compound types (unless they +are `#[repr(transparent)]`) as the alignment of these types would need to be +known in order to calculate field offsets and this would not be possible. Types +implementing only `DynSized` and `Pointee` could continue to be used in +compound types, but only as the last field. -As `RuntimeSized`, `DynSized` and `Unsized` are not default bounds, there is no +As `RuntimeSized`, `DynSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` for these traits. There is a potential performance impact within the trait system to adding @@ -264,12 +268,25 @@ checking them is skipped - this should be sound as all of these traits are implemented by the compiler and therefore this property can be guaranteed by the compiler implementation. +Traits which are a supertrait of any of the proposed traits will not +automatically imply the proposed trait in any bounds where the trait is +used, e.g. + +```rust +trait NewTrait: DynSized {} + +struct NewRc {} // equiv to `T: NewTrait + Sized` as today +``` + +If the user wanted `T: DynSized` then it would need to be written +explicitly. + ## Edition changes [edition-changes]: #edition-changes In the next edition, writing `?Sized` bounds would no longer be accepted and the compiler would suggest to users writing `DynSized` bounds instead. Existing `? -Sized` bounds can be trivially rewritten to `DynSized` bounds by `rustup`. +Sized` bounds can be trivially rewritten to `DynSized` bounds by `rustfix`. ## Auto traits and backwards compatibility [auto-traits-and-backwards-compatibility]: #auto-traits-and-backwards-compatibility @@ -280,18 +297,18 @@ type][changing_rules_of_rust]. Adding a new auto trait to the bounds of an existing function would typically be a breaking change, despite all types implementing the new auto trait, in -two cases: +three cases: 1. Callers with generic parameters would not have the new bound. For example, adding a new auto trait (which is not a default bound) as a bound to `std_fn` would cause `user_fn` to stop compiling as `user_fn`'s `T` would need the bound added too: - ```rust -fn user_fn(value: T) { std_fn(value) } -fn std_fn(value: T) { /* .. */ } -//~^ ERROR the trait bound `T: NewAutoTrait` is not satisfied - ``` + ```rust + fn user_fn(value: T) { std_fn(value) } + fn std_fn(value: T) { /* .. */ } + //~^ ERROR the trait bound `T: NewAutoTrait` is not satisfied + ``` Unlike with an arbitrary new auto trait, the proposed traits are all subtraits of `Sized` and every generic parameter either has a default bound @@ -299,14 +316,14 @@ fn std_fn(value: T) { /* .. */ } compatibility to be avoided. Relaxing the `Sized` bound of an existing function's generic parameters to - `RuntimeSized`, `DynSized` or `Unsized` would not break any callers, as those + `RuntimeSized`, `DynSized` or `Pointee` would not break any callers, as those callers' generic parameters must already have a `T: Sized` bound and therefore would already satisfy the new relaxed bound. Callers may now have a stricter bound than is necessary, but they likewise can relax their bounds without that being a breaking change. If an existing function had a generic parameter with a `?Sized` bound and - this bound were changed to `DynSized` or relaxed to `Unsized`, then callers' + this bound were changed to `DynSized` or relaxed to `Pointee`, then callers' generic parameters would either have a `T: Sized` or `T: ?Sized` bound: - If callers' generic parameters have a `T: Sized` bound then there would be @@ -342,19 +359,45 @@ fn std_fn(value: T) { /* .. */ } Like the previous case, due to the proposed traits being subtraits of `Sized`, and every trait object implementing `Sized`, adding a `RuntimeSized`, - `DynSized`, or `Unsized` bound to any existing generic parameter would be + `DynSized`, or `Pointee` bound to any existing generic parameter would be already satisfied. +3. Associated types of traits have default `Sized` bounds which can be being used. + For example, adding a `NewAutoTrait` bound to `Add::Output` breaks a function + which takes a `T: Add` and passes `::Output` to `size_of` as not all + types which implement `NewAutoTrait` will implement `Sized`. + + ```rust + trait Add { + type Output: NewAutoTrait; + } + + fn user_fn() { + std::mem::size_of::<::Output>() + //~^ ERROR the trait bound `::Output: Sized` is not satisfied + } + ``` + + Relaxing the bounds of an associated type is in effect giving existing + parameters a less restrictive bound which may not be suitable. + + Unfortunately, this means it is not possible to change existing associated + type bounds to any of the proposed sizedness traits from this RFC. While it + may be desirable to relax the `Sized` bound on associated types, it shouldn't + be necessary to do so in this RFC and thus this should not be considered a + blocker. It may be possible to work around this over an edition, which is + discussed in the future possibilities section. + Additionally, it is not expected that this RFC's additions would result in much churn within the ecosystem. All bounds in the standard library should be re-evaluated during the implementation of this RFC, but bounds in third-party crates need not be: Up-to-`RuntimeSized`-implementing types will primarily be used for localised -performance optimisation, and `Unsized`-only-implementing types will primarily be +performance optimisation, and `Pointee`-only-implementing types will primarily be used for localised FFI, neither is expected to be so pervasive throughout Rust software to the extent that all existing `Sized` or `?Sized` bounds would need to be immediately reconsidered in light of their addition. If a user of a -up-to-`RuntimeSized`-implementing type or a `Unsized`-only-implementing type did +up-to-`RuntimeSized`-implementing type or a `Pointee`-only-implementing type did encounter a bound that needed to be relaxed, this could be changed in a patch to the relevant crate without breaking backwards compatibility as-and-when such bounds are discovered. @@ -391,7 +434,7 @@ could be made to the standard library: - [`std::boxed::Box`][api_box] - `T: ?Sized` becomes `T: DynSized` - As before, this is not a breaking change and prevents types only - implementing `Unsized` from being used with `Box`, as these types do not have + implementing `Pointee` from being used with `Box`, as these types do not have the necessary size and alignment for allocation/deallocation. As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard @@ -415,7 +458,7 @@ There are various points of difference to the [prior art](#prior-art) related to - In contrast to [rfcs#1524][rfc_custom_dst], the `Sized` trait isn't extended as this wouldn't be sufficient to capture the range in sizedness that this RFC aims to capture (i.e. for scalable vectors with `RuntimeSized` or `extern type`s - with `Unsized`), even if it theoretically could enable Custom DSTs. + with `Pointee`), even if it theoretically could enable Custom DSTs. - In contrast to [rfcs#1993][rfc_opaque_data_structs], [rust#44469][pr_dynsized], [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_and_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], @@ -436,13 +479,13 @@ There are various points of difference to the [prior art](#prior-art) related to All of the trait names proposed in the RFC can be bikeshed and changed, they'll ultimately need to be decided but aren't the important part of the RFC. -## Why have `Unsized`? -It may seem that the `Unsized` trait is unnecessary as this is equivalent to the -absense of any bounds whatsoever, but having an `Unsized` trait is necessary to +## Why have `Pointee`? +It may seem that the `Pointee` trait is unnecessary as this is equivalent to the +absense of any bounds whatsoever, but having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined to be equivalent to `DynSized` and avoid complicated behaviour change over an edition. -Without `Unsized`, if a user wanted to remove all sizedness bounds from a generic +Without `Pointee`, if a user wanted to remove all sizedness bounds from a generic parameter then they would have two options: 1. Introduce new relaxed bounds (i.e. `?DynSized`), which has been found @@ -457,10 +500,10 @@ The latter is the only viable option, but this would complicate changing `?Sized` can be redefined to be equivalent to `DynSized` in all editions and the syntax can be removed in a future edition only because adding an -`Unsized` bound is equivalent to removing the `Sized` bound and imposing +`Pointee` bound is equivalent to removing the `Sized` bound and imposing no constraints on the sizedness of a parameter. -Without `Unsized`, a complicated migration would be necessary to change +Without `Pointee`, a complicated migration would be necessary to change all current uses of `?Sized` to `DynSized` (as these are equivalent) and then future uses of `?Sized` would now accept more types than `?Sized` previously did (they would now accept the `extern type`s). @@ -653,7 +696,7 @@ the `Sized` trait, summarised below: `size_of_val` and `align_of_val` with `extern type`s, but it is unclear why this is necessary given that `extern type`s are unstable. - - It does not include `RuntimeSized` or `Unsized`. + - It does not include `RuntimeSized` or `Pointee`. - Adding an explicit bound for `DynSized` does not remove the implicit bound for `Sized`. - [rust#49708: `extern type` cannot support `size_of_val` and `align_of_val`][issue_extern_types_align_size], [joshtriplett][author_joshtriplett], Apr 2018 @@ -777,6 +820,7 @@ the `Sized` trait, summarised below: like in this RFC and proposes deprecating `T: ?Sized` in place of `T: Unsized` and sometimes `T: DynSized`. Adding a bound for any of `DynSized` or `Unsized` removes the default `Sized` bound. + - `Unsized` is the same as this RFC's `Pointee` - As described below it is the closest inspiration for this RFC. There are some even older RFCs that have tangential relevance that are listed @@ -800,9 +844,9 @@ of the `RuntimeSized` trait, as the scalable vector types proposal in [RFC 3268] To summarise the above exhaustive listing of prior art: -- No previous works have proposed a `RuntimeSized` trait or a `Unsized` trait - (with the exception of [Sized, DynSized, and Unsized][blog_dynsized_unsized] for - `Unsized`), only `DynSized`. +- No previous works have proposed an equivalent of a `RuntimeSized` trait or + a `Pointee` trait (with the exception of[Sized, DynSized, and Unsized][blog_dynsized_unsized] + for `Pointee`), only `DynSized`. - One proposal proposed adding a marker type that as a field would result in the containing type no longer implementing `Sized`. - Often proposals focused at Custom DSTs preferred to combine the @@ -860,13 +904,17 @@ this proposal: rustc, which currently works because it has a presumed alignment of one. This would be prohibited by the RFC as written, but relaxing this is listed as a future possibility. - - Adding limitations to the use of `Unsized` types in structs could be left + - Adding limitations to the use of `Pointee` types in structs could be left as a follow-up as it is more related to `extern type` than extending `Sized` - anyway. Implementation of this RFC would not create any `Unsized` types. + anyway. Implementation of this RFC would not create any `Pointee` types. - How would this interact with proposals to split alignment and sizedness into separate traits? - Some prior art had different rules for automatically implementing `DynSized` on structs/enums vs on unions - are those necessary for these traits? +- `Pointee` is already the name of an unstable trait, so it may make sense to rename + this to something else, but it seemed better than `Unsized` as it was named + in earlier drafts. +- How serious are the limitations on changing the bounds of associated types? # Future possibilities [future-possibilities]: #future-possibilities @@ -875,17 +923,17 @@ this proposal: other delineations in sized-ness that make sense to be drawn. - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2], something like `Sized: MetaSized`, `MetaSized: RuntimeSized`, `RuntimeSized: DynSized` and - `DynSized: Unsized`. + `DynSized: Pointee`. - The requirement that users cannot implement any of these traits could be relaxed in future to support custom DSTs or any other proposed feature which required it. -- Relax prohibition of `Unsized` fields in some limited contexts, for example if +- Relax prohibition of `Pointee` fields in some limited contexts, for example if it is the final field and the offset of it is never computed. -- In addition to the default bound changes described above, the default - `T: Sized` bound could be omitted if any other bound `T: Trait` had an explicit - supertrait of `RuntimeSized`, `DynSized` or `Unsized`. This would allow - boilerplate `?Sized` bounds to be removed. - - Credit to [Sized, DynSized, and Unsized][blog_dynsized_unsized] for this idea. +- Depending on a trait which has one of the proposed traits as a supertrait could + imply a bound of the proposed trait, enabling the removal of boilerplate. +- Consider allowing associated type bounds to be relaxed over an edition. + - i.e. `type Output: if_rust_2021(Sized) + NewAutoTrait` or something like that, + out of scope for this RFC. - This proposal is compatible with adding Custom DSTs in future. - Leveraging the already accepted [rfcs#2580][rfc_pointer_metadata_vtable] and taking an approach similar to [rfcs#2594][rfc_custom_dst_electric_boogaloo] From 20e745476c1408d48037c9f0481de941c3bfd1f1 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 17 Oct 2024 13:03:48 +0100 Subject: [PATCH 04/45] add reasoning about constness --- text/0000-sized-hierarchy.md | 95 +++++++++++++++++++++++++++++++++--- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index bf93ebd7b19..91780018655 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -2,6 +2,7 @@ - Start Date: 2024-09-30 - RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + # Summary [summary]: #summary @@ -184,8 +185,9 @@ Introduce three new marker traits, `RuntimeSized`, `DynSized` and `Pointee`, creating a "hierarchy" of `Sized` traits: - `Pointee` - - Types that may not have a knowable size at all. - - i.e. Types which can be used from behind a pointer. + - Types that may not have a knowable size at all and which can be + used from behind a pointer. + - i.e. the type's size is a function of variables unknown to Rust - `Pointee` will be implemented for: - `DynSized` types - if a type's size is computable at runtime then it may or @@ -195,6 +197,8 @@ creating a "hierarchy" of `Sized` traits: - In practice, every type will implement `Pointee`. - `DynSized` - Types whose size is computable at runtime. + - i.e. the type's size is a function of the type, the target and + the value - `DynSized` is a subtrait of `Pointee`. - `DynSized` will be implemented for: - `RuntimeSized` types @@ -206,6 +210,8 @@ creating a "hierarchy" of `Sized` traits: - compound types where every element is `DynSized` - `RuntimeSized` - Types whose size is statically known at runtime. + - i.e. the type's size is a function of the type, the target + and the runtime environment - `RuntimeSized` is a subtrait of `DynSized`. - `RuntimeSized` will be implemented for: - `Sized` types @@ -215,6 +221,7 @@ creating a "hierarchy" of `Sized` traits: - compound types where every element is `RuntimeSized` - `Sized` - Types whose size is statically known at compilation time. + - i.e. the type's size is a function of the type and the target - `Sized` is a subtrait of `RuntimeSized`. - `Sized` will be implemented for: - primitives `iN`, `uN`, `fN`, `char`, `bool` @@ -418,9 +425,6 @@ could be made to the standard library: - `DynSized` would not be implemented by `extern type`s from [rfcs#1861] [rfc_extern_types], which would prevent these functions from being invoked on types which have no known size or alignment. - - `size_of_val` and `align_of_val` are currently `const` unstably and these - types would no longer be able to be made `const` as this would require `T: - Sized`, not `T: DynSized`. - [`std::clone::Clone`][api_clone] - `Clone: Sized` becomes `Clone: RuntimeSized` - `RuntimeSized` is implemented by more types than `Sized` and by all types @@ -440,6 +444,24 @@ could be made to the standard library: As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard library would need to be reviewed and updated as appropriate. +## `RuntimeSized` and constness + +`RuntimeSized` is defined as types whose size is only known at runtime, not at +compilation time (or put otherwise, that the type's size is a function of its +runtime environment, amongst other things). In a `const` context, the runtime +environment used when determining the size of a up-to-`RuntimeSized` type is +the runtime environment of the host. This allows +[`std::mem::size_of_val`][api_size_of_val] to be made a `const` function. + +For example, if the size of a up-to-`RuntimeSized` register were determined by +checking the value of a register, then that register would be checked on the host +during constant evaluation. This is largely a theoretical concern as it is not +likely to be possible to get values of the up-to-`RuntimeSized` types in a +`const` context. + +Alternatives to this are discussed in the [Host runtime environment in `const` +contexts][host-runtime-environment-in-const-contexts] section. + # Drawbacks [drawbacks]: #drawbacks @@ -460,7 +482,7 @@ There are various points of difference to the [prior art](#prior-art) related to aims to capture (i.e. for scalable vectors with `RuntimeSized` or `extern type`s with `Pointee`), even if it theoretically could enable Custom DSTs. - In contrast to [rfcs#1993][rfc_opaque_data_structs], [rust#44469][pr_dynsized], - [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_and_dynsized] and + [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], none of the traits proposed in this RFC are default bounds and therefore do not need to support being relaxed bounds (i.e. no `?RuntimeSized`), which avoids @@ -476,10 +498,14 @@ There are various points of difference to the [prior art](#prior-art) related to trait as in [rfcs#2594][rfc_custom_dst_electric_boogaloo]. ## Bikeshedding +[bikeshedding]: #bikeshedding + All of the trait names proposed in the RFC can be bikeshed and changed, they'll ultimately need to be decided but aren't the important part of the RFC. ## Why have `Pointee`? +[why-have-pointee]: #why-have-pointee + It may seem that the `Pointee` trait is unnecessary as this is equivalent to the absense of any bounds whatsoever, but having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined to be equivalent to `DynSized` @@ -511,7 +537,59 @@ previously did (they would now accept the `extern type`s). In [rfcs#3396][rfc_extern_types_v2], `MetaSized` was introduced and used a similar mechanism over an edition to redefine `?Sized`. +## Host runtime environment in `const` contexts +[host-runtime-environment-in-const-contexts]: #host-runtime-environment-in-const-contexts + +As previously described, the runtime environment of a up-to-`RuntimeSized` +type in `const` context is defined to be the host's runtime environment. + +However, this is not the only possible solution - using +[`std::mem::size_of_val`][api_size_of_val] as an example: + +| `size_of_val` | Runtime Context | `const` Context | +| -------------- | ------------------------------------------------ | ------------------- | +| `Sized` | Trivially known | Trivially known | +| `RuntimeSized` | From runtime environment (i.e. check a register) | ??? | +| `DynSized` | Computed from value | Computed from value | + +- Do not allow `size_of_val` to be a `const fn` + - This would prevent any function which may need `size_of_val` from + being `const` (and any function which depends on that function, etc etc). +- Define a `size_of_val_runtime` and `size_of_val_const` + - It would be impossible to define a function which accepted both `Sized` + and `DynSized` but not `RuntimeSized` without having to reorder + the proposed hierarchy (which has many complications of its own). +- Try to find an alternative hierarchy of traits + - Instead of having a linear hierarchy, have a diamond-shaped hierarchy or + something else - this adds significantly more complexity to the proposal. +- Define the runtime environment for `RuntimeSized` in a `const` context + +Of the potential alternatives, defining the runtime environment to be the host +in `const` contexts has the least complexity, but it is not immediately clear +that it is a feasible solution. + +| `size_of_val` | Runtime Context | `const` Context | +| -------------- | ---------------------------------- | --------------------------------- | +| `Sized` | Trivially known | Trivially known | +| `RuntimeSized` | From runtime environment of target | From runtime environment of host | +| `DynSized` | Computed from value | Computed from value | + +This solution necessitates that the requirements of a given type of its runtime +environment (for example, that it be a specific target that has a register containing +its size) are also satisfied by the host. If a value of one of these types exists in +a `const` context to be provided to `size_of_val` then there must exist `const` functions +with the appropriate `#[target_feature]` and/or `#[cfg]` attributes to create it and +therefore the host must satisfy the type's requirements (and the implementation of the +`size_of_val` intrinsic in the interpreter has likely been updated to know how to +determine the size). + +In practice, there likely are not appropriate `const` functions which would enable +values of these types to exist in a `const` context, so this is a largely theoretical +concern (while still allowing `size_of_val` to be defined with just a `DynSized` bound). + ## Alternatives to this RFC +[alternatives-to-this-rfc]: #alternatives-to-this-rfc + There are not many alternatives to this RFC to unblock `extern type`s and scalable vectors: @@ -787,6 +865,9 @@ the `Sized` trait, summarised below: - Automatically implemented for all types with an alignment (includes all `Sized` types). - `Aligned` is a supertrait of `Sized`. + - It's not immediately obvious how `Aligned` would work with this RFC's proposed + traits - would `Aligned` still be a supertrait of `Sized` or of `Pointee`? would + you need a `SizedAligned`, `RuntimeAligned`, `DynAligned` and `Aligned`? - [rfcs#3396: Extern types v2][rfc_extern_types_v2], [Skepfyr][author_skepfyr], Feb 2023 - Proposes a `MetaSized` trait for types whose size and alignment can be determined solely from pointer metadata without having to dereference the @@ -914,6 +995,8 @@ this proposal: - `Pointee` is already the name of an unstable trait, so it may make sense to rename this to something else, but it seemed better than `Unsized` as it was named in earlier drafts. + - Could the existing `std::ptr::Pointee` trait be used as the supertrait + of all other sizedness traits? - How serious are the limitations on changing the bounds of associated types? # Future possibilities From 8a8c5a9c69e89d8fc4cff3d0ad7c1e9a5051dc16 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 17 Oct 2024 17:28:44 +0100 Subject: [PATCH 05/45] add diamond hierarchy --- text/0000-sized-hierarchy.md | 686 +++++++++++++++++++---------------- 1 file changed, 382 insertions(+), 304 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 91780018655..a6b181457bb 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -28,10 +28,53 @@ implemented by the compiler on any type that has a statically known size. All type parameters have a default bound of `Sized` and `?Sized` syntax can be used to remove this bound. +There are two functions in the standard library which can be used to get a size, +[`std::mem::size_of`][api_size_of] and [`std::mem::size_of_val`][api_size_of_val]: + +```rust= +pub const fn size_of() -> usize { + /* .. */ +} + +pub fn size_of_val(val: &T) -> usize +where + T: ?Sized, +{ + /* .. */ +} +``` + Due to `size_of_val::`'s `T: ?Sized` bound, it is expected that the size of a `?Sized` type can be computable at runtime, and therefore a `T` with `T: ?Sized` cannot be a type with no size. +## Terminology +[terminology]: #terminology + +In the Rust community, "unsized" and "dynamically sized" are often used +interchangeably to describe any type that does not implement `Sized`. This is +unsurprising as any type which does not implement `Sized` is necessarily +"unsized" and currently the only types this description captures are those which +are dynamically sized. + +In this RFC, a distinction is made between "unsized" and "dynamically sized" +types. Unsized types is used to refer only to those which have no known +size/alignment, such as those described by [the extern types +RFC][rfc_extern_types]. Dynamically-sized types describes those types whose size +cannot be known statically at compilation time and must be computed at runtime. + +Within this RFC, no terminology is introduced to describe all types which do not +implement `Sized` in the same sense as "unsized" is colloquially used. + +Furthermore, throughout the RFC, the terminology "`Trait` types" will be used +to refer to those types which implement `Trait` and all of its supertraits but +none of its subtraits. For example, a `DynSized` type would be a type which +implements `DynSized`, `DynRuntimeSized` and `Pointee`, but not `Sized`. +`[usize]` would be referred to as a "`DynSized` type". + +Throughout the RFC, the bounds on the generic parameters of a function may be +referred to simply as the bounds on the function (e.g. "the caller's bounds"). + # Motivation [motivation]: #motivation @@ -90,24 +133,6 @@ Introducing a hierarchy of `Sized` traits will enable extern types to implement a trait for unsized types and therefore not meet the bounds of `size_of_val` and `align_of_val`. -# Terminology -[terminology]: #terminology - -In the Rust community, "unsized" and "dynamically sized" are often used -interchangeably to describe any type that does not implement `Sized`. This is -unsurprising as any type which does not implement `Sized` is necessarily -"unsized" and the only types this description captures are those which are -dynamically sized. - -In this RFC, a distinction is made between "unsized" and "dynamically sized" -types. Unsized types is used to refer only to those which have no known -size/alignment, such as those described by [the extern types -RFC][rfc_extern_types]. Dynamically-sized types describes those types whose size -cannot be known statically at compilation time and must be computed at runtime. - -Within this RFC, no terminology is introduced to describe all types which do not -implement `Sized` in the same sense as "unsized" is colloquially used. - # Guide-level explanation [guide-level-explanation]: #guide-level-explanation Most types in Rust have a size known at compilation time, such as `u32` or @@ -118,111 +143,189 @@ known as *dynamically-sized types*, their size must be computed at runtime. There are also types with a statically known size but only at runtime, and *unsized types* with no size whatsoever. -Rust uses marker traits to indicate whether a type is sized, when this is known -and if the size needs to be computed. There are four traits related to the size of -a type in Rust: `Sized` , `RuntimeSized`, `DynSized` and `Pointee`. +Various parts of Rust depend on knowledge of the size of a type to work, for +example: + +- [`std::mem::size_of_val`][api_size_of_val] computes the size of a value, + and thus cannot accept `extern type`s which have no size, and this should + be prevented by the type system. +- Rust allows dynamically-sized types to be used as struct fields, but the + alignment of the type must be known, which is not the case for `extern type`s. +- Allocation and deallocation of an object with `Box` requires knowledge of + its size and alignment, which `extern type`s do not have. +- For a value type to be allocated on the stack, it needs to have statically + known size, which dynamically-sized and unsized types do not have (but + sized and "runtime sized" types do). + +Rust uses marker traits to indicate the necessary knowledge required to know +the size of a type, if it can be known. There are five traits related to the size +of a type in Rust: `Sized`, `RuntimeSized`, `DynSized`, `RuntimeDynSized` and +`Pointer`. *There is already an unstable trait named `Pointee`, this isn't intended to replace that trait and either trait may need to be renamed so as not to overlap, but `Pointee` made sense as a placeholder name for this.* -Everything which implements `Sized` also implements `RuntimeSized`, and -likewise with `RuntimeSized` with `DynSized`, and `DynSized` with `Pointee`. +`Sized` is a supertrait of both `RuntimeSized` and `DynSized`, so every type of +which implements `Sized` also implements `RuntimeSized` and `DynSized`. +`RuntimeSized` and `DynSized` are both supertraits of `DynRuntimeSized` and +`DynRuntimeSized` is a supertrait of `Pointee`. ``` -┌─────────────────────────────────────────────────┐ -│ ┌─────────────────────────────────────┐ │ -│ │ ┌────────────────────────┐ │ │ -│ │ │ ┌───────┐ │ │ │ -│ │ │ │ Sized │ RuntimeSized │ DynSized │ Pointee │ -│ │ │ └───────┘ │ │ │ -│ │ └────────────────────────┘ │ │ -│ └─────────────────────────────────────┘ │ -└─────────────────────────────────────────────────┘ +┌───────────────────────────────────────────────────────────────┐ +│ ┌───────────────────────────────────────────────────────────┐ │ +│ │ ┌──────────────────────────────┐┌───────────────────────┐ │ │ +│ │ │ ┌────────────────────────────┴┴─────────────────────┐ │ │ │ +│ │ │ │ Sized │ │ │ │ +│ │ │ │ {type, target} │ │ │ │ +│ │ │ └────────────────────────────┬┬─────────────────────┘ │ │ │ +│ │ │ RuntimeSized ││ DynSized │ │ │ +│ │ │ {type, target, runtime env} ││ {type, target, value} │ │ │ +│ │ └──────────────────────────────┘└───────────────────────┘ │ │ +│ │ DynRuntimeSized │ │ +│ │ {type, target, runtime env, value} │ │ +│ └───────────────────────────────────────────────────────────┘ │ +│ Pointee │ +│ {*} │ +└───────────────────────────────────────────────────────────────┘ ``` -`Pointee` is implemented by any type which may or may not be sized (these -types can always be used from behind a pointer, hence the name), which is -to say, every type - from a `u32` which is obviously sized (32 bits) to an -`extern type` (from [rfcs#1861][rfc_extern_types]) which has no known size. +`Sized` is implemented on types which require knowledge of only the type and +target platform in order to compute their size. For example, `usize` implements +`Sized` as knowing only the type is `usize` and the target is +`aarch64-unknown-linux-gnu` then we can know the size is eight bytes, and likewise +with `armv7-unknown-linux-gnueabi` and a size of four bytes. + +`DynSized` requires more knowledge than `Sized` to compute the size: it may +additionally require a value (therefore `size_of` is not implemented for +`DynSized`, only `size_of_val`). For example, `[usize]` implements `DynSized` +as knowing the type and target is not sufficient, the number of elements in +the slice must also be known, which requires having the value. + +Like `DynSized`, `RuntimeSized` also requires more knowledge than `Sized` to +compute the size: it may additionally require knowledge of the "runtime +environment". For example, `svint8_t` is a scalable vector type (from +[rfc#3268][rfc_scalable_vectors]) whose length depends on the specific +implementation of the target (i.e. it may be 128 bits on one processor and +256 bits on another). `RuntimeSized`-types can be used with `size_of` and +`size_of_val` (as a value is not required), but cannot be used in a `const` +context. + +`DynRuntimeSized` combines the requirements from `DynSized` and +`RuntimeSized` - these types may need a value and knowledge of the type, +target, and runtime environment. For example, `[svint8_t]` requires a value +to know how many elements there are, and then information from the runtime +environment to know the size of a `svint8_t`. + +`Pointee` is implemented by any type that can be used behind a pointer, which is +to say, every type (put otherwise, these types may or may not be sized at all). +For example, `Pointee` is therefore implemented on a `u32` which is trivially +sized, a `[usize]` which is dynamically sized, a `svint8_t` which is runtime +sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which has no +known size. -`DynSized` is a subset of `Pointee`, and excludes those types whose sizes -cannot be computed at runtime. +All type parameters have an implicit bound of `Sized` which will be automatically +removed if a `RuntimeSized`, `DynSized`, `DynRuntimeSized` or `Pointee` bound is +present instead. + +Prior to the introduction of `RuntimeSized`, `DynSized`, `DynRuntimeSized` and +`Pointee`, `Sized`'s implicit bound could be removed using the `?Sized` syntax, +which is now equivalent to a `DynRuntimeSized` bound and will be deprecated in +the next edition. + +As `RuntimeSized` and `DynRuntimeSized` types may require knowledge of the runtime +environment for their size to be computed, they cannot be used in `const` contexts. +`const` functions with a `RuntimeSized` or `DynRuntimeSized` bound will have those +bounds upgraded to `Sized` and `DynSized` bounds respectively when called from a +`const` context. -Similarly, `RuntimeSized` is a subset of `DynSized`, and excludes those types -whose sizes are not statically known at runtime. Scalable vectors (from -[rfc#3268][rfc_scalable_vectors]) have an unknown size at compilation time but -statically known size at runtime. +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation -And finally, `Sized` is a subset of `RuntimeSized`, and excludes those types -whose sizes are not statically known at compilation time. +Introduce four new marker traits, `RuntimeSized`, `DynSized`, `DynRuntimeSized` +and `Pointee`, creating a "non-linear hierarchy" of `Sized` traits: -All type parameters have an implicit bound of `Sized` which will be automatically -removed if a `RuntimeSized`, `DynSized` or `Pointee` bound is present instead. -Prior to the introduction of `RuntimeSized`, `DynSized` and `Pointee`, `Sized`'s -implicit bound could be removed using the `?Sized` syntax, which is now -equivalent to a `DynSized` bound and will be deprecated in the next edition. +``` + ┌────────────────┐ + │ Sized │ + │ {type, target} │ + └────────────────┘ + │ + ┌──────────────┴─────────────┐ + │ │ +┌─────────────┴───────────────┐┌───────────┴───────────┐ +│ RuntimeSized ││ DynSized │ +│ {type, target, runtime env} ││ {type, target, value} │ +└─────────────────────────────┘└───────────────────────┘ + │ │ + └──────────────┬─────────────┘ + │ + ┌──────────────────┴─────────────────┐ + │ DynRuntimeSized │ + │ {type, target, runtime env, value} │ + └────────────────────────────────────┘ + │ + ┌────┴────┐ + │ Pointee │ + │ {*} │ + └─────────┘ +``` -Various parts of Rust depend on knowledge of the size of a type to work, for -example: +Or, in Rust syntax: -- `std::mem::size_of_val` computes the runtime size of a value, and thus - cannot accept `extern type`s, and this should be prevented by the type - system. -- Rust allows dynamically-sized types to be used as struct fields, but the - alignment of the type must be known, which is not the case for - `extern type`s. -- Allocation and deallocation of an object with `Box` requires knowledge of - its size and alignment, which `extern type`s do not have. -- For a value type to be allocated on the stack, it needs to have statically - known size, which dynamically-sized and unsized types do not have (but - sized and "runtime sized" types do). +```rust= +trait Sized: RuntimeSized + DynSized {} -# Reference-level explanation -[reference-level-explanation]: #reference-level-explanation +trait RuntimeSized: DynRuntimeSized {} + +trait DynSized: DynRuntimeSized {} + +trait DynRuntimeSized: Pointee {} -Introduce three new marker traits, `RuntimeSized`, `DynSized` and `Pointee`, -creating a "hierarchy" of `Sized` traits: +trait Pointee {} +``` + +Like `Sized`, implementations of the four proposed traits are automatically +generated by the compiler and cannot be implemented manually: - `Pointee` - - Types that may not have a knowable size at all and which can be - used from behind a pointer. - - i.e. the type's size is a function of variables unknown to Rust + - Types that which can be used from behind a pointer (they may or may + not have a size). - `Pointee` will be implemented for: - - `DynSized` types - - if a type's size is computable at runtime then it may or - may not have a size (it does) + - `DynRuntimeSized` types - `extern type`s from [rfcs#1861][rfc_extern_types] - compound types where every element is `Pointee` - In practice, every type will implement `Pointee`. +- `DynRuntimeSized` + - Types whose size is computable given a value, and knowledge of the + type, target and runtime environment. + - `DynRuntimeSized` is a subtrait of `Pointee` + - `DynRuntimeSized` will be implemented for: + - `DynSized` types + - `RuntimeSized` types + - slices `[T]` where every element is `DynRuntimeSized` + - compound types where every element is `DynRuntimeSized` - `DynSized` - - Types whose size is computable at runtime. - - i.e. the type's size is a function of the type, the target and - the value - - `DynSized` is a subtrait of `Pointee`. + - Types whose size is computable given a value, and knowledge of the + type and target. + - `DynSized` is a subtrait of `DynRuntimeSized`. - `DynSized` will be implemented for: - - `RuntimeSized` types - - if a type's size is statically known at runtime time then it is - trivially computable at runtime - - slices `[T]` + - `Sized` types + - slices `[T]` where every element is `Sized` - string slice `str` - trait objects `dyn Trait` - compound types where every element is `DynSized` - `RuntimeSized` - - Types whose size is statically known at runtime. - - i.e. the type's size is a function of the type, the target - and the runtime environment - - `RuntimeSized` is a subtrait of `DynSized`. + - Types whose size is computable given knowledge of the type, target and + runtime environment. + - `RuntimeSized` is a subtrait of `DynRuntimeSized`. - `RuntimeSized` will be implemented for: - `Sized` types - - if a type's size is statically known at compilation time then it - is also statically known at runtime - scalable vectors from [rfcs#3268][rfc_scalable_vectors] - compound types where every element is `RuntimeSized` - `Sized` - - Types whose size is statically known at compilation time. - - i.e. the type's size is a function of the type and the target - - `Sized` is a subtrait of `RuntimeSized`. + - Types whose size is computable given knowledge of the type and target. + - `Sized` is a subtrait of `RuntimeSized` and `DynSized`. - `Sized` will be implemented for: - primitives `iN`, `uN`, `fN`, `char`, `bool` - pointers `*const T`, `*mut T` @@ -231,40 +334,34 @@ creating a "hierarchy" of `Sized` traits: - never type `!` - unit tuple `()` - closures and generators - - compound types where every field is `Sized` + - compound types where every element is `Sized` - anything else which currently implements `Sized` -Like `Sized`, implementations of these three traits are automatically generated -by the compiler and cannot be implemented manually. - -In the compiler, `?Sized` would be made syntactic sugar for a `DynSized` -bound (and eventually removed in an upcoming edition). A `DynSized` bound is -equivalent to a `?Sized` bound: all values in Rust today whose types do not -implement `Sized` are valid arguments to `std::mem::size_of_val` and as such -have a size which can be computed at runtime, and therefore will implement -`DynSized`. As there are currently no `extern type`s or other types which -would not implement `DynSized`, every type in Rust today which would satisfy -a `?Sized` bound would satisfy a `DynSized` bound. +In the compiler, `?Sized` would be made syntactic sugar for a `DynRuntimeSized` +bound (and eventually removed in an upcoming edition). A `DynRuntimeSized` +bound is equivalent to a `?Sized` bound as all values in Rust today whose types +do not implement `Sized` are valid arguments to `std::mem::size_of_val` and as +such have a size which can be computed at runtime, and therefore will implement +`DynRuntimeSized`. As there are currently no `extern type`s or other types which +would not implement `DynRuntimeSized`, every type in Rust today which would +satisfy a `?Sized` bound would satisfy a `DynRuntimeSized` bound. A default implicit bound of `Sized` is added by the compiler to every type parameter `T` that does not have an explicit `Sized`, `?Sized`, `RuntimeSized`, -`DynSized` or `Pointee` bound. This is somewhat unintuitive as typically adding -a trait bound does not remove the implementation of another trait bound from -being implemented, however it's debatable whether this is more or less confusing -than existing `?Sized` bounds. +`DynSized`, `DynRuntimeSized` or `Pointee` bound. -An implicit `DynSized` bound is added to the `Self` type of traits. Like +An implicit `DynRuntimeSized` bound is added to the `Self` type of traits. Like implicit `Sized` bounds, this is omitted if an explicit `Sized`, `RuntimeSized` -bound is present. +or `DynSized` bound is present. -Types implementing only `Pointee` cannot be used in compound types (unless they -are `#[repr(transparent)]`) as the alignment of these types would need to be -known in order to calculate field offsets and this would not be possible. Types -implementing only `DynSized` and `Pointee` could continue to be used in -compound types, but only as the last field. +`Pointee` types cannot be used in compound types (unless they are +`#[repr(transparent)]`) as the alignment of these types would need to be known +in order to calculate field offsets and this would not be possible. +`RuntimeSized`, `DynSized`, `DynRuntimeSized` or `Pointee` types could continue +to be used in compound types, but only as the last element. -As `RuntimeSized`, `DynSized` and `Pointee` are not default bounds, there is no -equivalent to `?Sized` for these traits. +As `RuntimeSized`, `DynSized`, `RuntimeDynSized`, and `Pointee` are not default +bounds, there is no equivalent to `?Sized` for these traits. There is a potential performance impact within the trait system to adding supertraits to `Sized`, as implementation of these supertraits will need to be @@ -272,8 +369,7 @@ proven whenever a `Sized` obligation is being proven (and this happens very frequently, being a default bound). It may be necessary to implement an optimisation whereby `Sized`'s supertraits are assumed to be implemented and checking them is skipped - this should be sound as all of these traits are -implemented by the compiler and therefore this property can be guaranteed by -the compiler implementation. +implemented by the compiler and therefore this property can be guaranteed. Traits which are a supertrait of any of the proposed traits will not automatically imply the proposed trait in any bounds where the trait is @@ -285,15 +381,48 @@ trait NewTrait: DynSized {} struct NewRc {} // equiv to `T: NewTrait + Sized` as today ``` -If the user wanted `T: DynSized` then it would need to be written -explicitly. +If the user wanted `T: DynSized` then it would need to be written explicitly. ## Edition changes [edition-changes]: #edition-changes In the next edition, writing `?Sized` bounds would no longer be accepted and the -compiler would suggest to users writing `DynSized` bounds instead. Existing `? -Sized` bounds can be trivially rewritten to `DynSized` bounds by `rustfix`. +compiler would suggest users write `DynRuntimeSized` bounds instead. Existing +`?Sized` bounds will be trivially rewritten to `DynRuntimeSized` bounds by `rustfix`. + +## Constness +[constness]: #constness + +`RuntimeSized` and `DynRuntimeSized` types may need knowledge of the runtime +environment which prevents their use in a `const` context. + +In a `const` context, there is no runtime environment which can be used to +determine the size of these types. + +| [`size_of`][api_size_of] | Runtime Context | `const` Context | +| ------------------------ | ------------------------- | ------------------- | +| `Sized` | Trivially known | Trivially known | +| `RuntimeSized` | Check runtime environment | ❌ | + +| [`size_of_val`][api_size_of_val] | Runtime Context | `const` Context | +| -------------------------------- | ----------------------------------------------- | ------------------- | +| `Sized` | Trivially known | Trivially known | +| `RuntimeSized` | Check runtime environment | ❌ | +| `DynSized` | Computed from value | Computed from value | +| `DynRuntimeSized` | Computed from value + check runtime environment | ❌ | + +However, in a non-`const` context, `size_of` and `size_of_val` should be +callable with `RuntimeSized` types, and `size_of_val` should be callable with +`DynRuntimeSized` types. + +All `RuntimeSized` bounds will automatically be upgraded to `Sized` bounds when +called from a `const` context, and similarly `DynRuntimeSized` bounds will +automatically be upgraded to `DynSized` bounds when called from a `const` +context. + +It is not expected that this will be frequently encountered by users, mostly +as it will not be possible to obtain values of `RuntimeSized` types in +`const` contexts. ## Auto traits and backwards compatibility [auto-traits-and-backwards-compatibility]: #auto-traits-and-backwards-compatibility @@ -306,10 +435,9 @@ Adding a new auto trait to the bounds of an existing function would typically be a breaking change, despite all types implementing the new auto trait, in three cases: -1. Callers with generic parameters would not have the new bound. For example, - adding a new auto trait (which is not a default bound) as a bound to `std_fn` - would cause `user_fn` to stop compiling as `user_fn`'s `T` would need the bound - added too: +1. Callers would not have the new bound. For example, adding a new auto trait + (which is not a default bound) as a bound to `std_fn` would cause `user_fn` + to stop compiling as `user_fn`'s `T` would need the bound added too: ```rust fn user_fn(value: T) { std_fn(value) } @@ -318,40 +446,41 @@ three cases: ``` Unlike with an arbitrary new auto trait, the proposed traits are all - subtraits of `Sized` and every generic parameter either has a default bound - of `Sized` or has a `?Sized` bound, which enables this risk of backwards - compatibility to be avoided. - - Relaxing the `Sized` bound of an existing function's generic parameters to - `RuntimeSized`, `DynSized` or `Pointee` would not break any callers, as those - callers' generic parameters must already have a `T: Sized` bound and therefore - would already satisfy the new relaxed bound. Callers may now have a stricter - bound than is necessary, but they likewise can relax their bounds without that - being a breaking change. - - If an existing function had a generic parameter with a `?Sized` bound and - this bound were changed to `DynSized` or relaxed to `Pointee`, then callers' - generic parameters would either have a `T: Sized` or `T: ?Sized` bound: - - - If callers' generic parameters have a `T: Sized` bound then there would be - no breaking change as `T: Sized` implies the changed or relaxed bound. - - If callers' generic parameter have a `T: ?Sized` bound then this is - equivalent to a `T: DynSized` bound, as described earlier. Therefore there would - be no change as `T: DynSized` implies the changed or relaxed bound. - - ```rust - fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized - fn std_fn(value: T) { /* .. */ } - ``` - - If an existing function's generic parameter had a `?Sized` bound and this - bound were changed to `RuntimeSized` then this *would* be a breaking change, but - it is not expected that this change would be applied to any existing functions - in the standard library. + subtraits of `Sized` and every generic parameter already either has a + default bound of `Sized` or has a `?Sized` bound, which enables this risk + of backwards compatibility to be avoided. + + Relaxing the `Sized` bound of an existing function to `RuntimeSized`, + `DynSized`, `DynRuntimeSized` or `Pointee` would not break any callers, + as those callers must already have a `T: Sized` bound and therefore would + already satisfy the new relaxed bound. Callers may now have a stricter + bound than is necessary, but they likewise can relax their bounds without + that being a breaking change. + + If an existing function had a `?Sized` bound and this bound were changed to + `DynRuntimeSized` or relaxed to `Pointee`, then callers would either have a + `T: Sized` or `T: ?Sized` bound: + + - If callers have a `T: Sized` bound then there would be no breaking change + as `T: Sized` implies the changed or relaxed bound. + - If callers have a `T: ?Sized` bound then this is equivalent to a + `T: DynRuntimeSized` bound, as described earlier. Therefore there would + not be a breaking change as `T: DynRuntimeSized` implies the changed or + relaxed bound. + + ```rust + fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized + fn std_fn(value: T) { /* .. */ } + ``` + + If an existing function had a `?Sized` bound and this bound were changed to + `RuntimeSized` then this *would* be a breaking change, but it is not expected + that this change would be applied to any existing functions in the standard + library. - The proposed traits in this RFC are only a non-breaking change because the - new auto traits are being added as subtraits of `Sized`, adding supertraits of - `Sized` would be a breaking change. + The proposed traits in this RFC are only a non-breaking change because the + new auto traits being added are subtraits of `Sized`, adding supertraits of + `Sized` would be a breaking change. 2. Trait objects passed by callers would not imply the new trait. For example, adding a new auto trait as a bound to `std_fn` would cause `user_fn` to stop @@ -365,18 +494,17 @@ three cases: ``` Like the previous case, due to the proposed traits being subtraits of - `Sized`, and every trait object implementing `Sized`, adding a `RuntimeSized`, - `DynSized`, or `Pointee` bound to any existing generic parameter would be - already satisfied. + `Sized`, and every trait object implementing `Sized`, a new `RuntimeSized`, + `DynSized`, `DynRuntimeSized`, or `Pointee` bound would be already satisfied. -3. Associated types of traits have default `Sized` bounds which can be being used. - For example, adding a `NewAutoTrait` bound to `Add::Output` breaks a function +3. Associated types of traits have default `Sized` bounds which may be being used. + For example, relaxing a `Sized` bound on `Add::Output` breaks a function which takes a `T: Add` and passes `::Output` to `size_of` as not all - types which implement `NewAutoTrait` will implement `Sized`. + types which implement the relaxed bound will implement `Sized`. ```rust trait Add { - type Output: NewAutoTrait; + type Output: RuntimeSized; } fn user_fn() { @@ -395,19 +523,22 @@ three cases: blocker. It may be possible to work around this over an edition, which is discussed in the future possibilities section. -Additionally, it is not expected that this RFC's additions would result in much -churn within the ecosystem. All bounds in the standard library should be re-evaluated -during the implementation of this RFC, but bounds in third-party crates need not be: +## Risk of churn +[risk-of-churn]: #risk-of-churn + +It is not expected that this RFC's additions would result in much churn within +the ecosystem. All bounds in the standard library should be re-evaluated +during the implementation of this RFC, but bounds in third-party crates need not +be. -Up-to-`RuntimeSized`-implementing types will primarily be used for localised -performance optimisation, and `Pointee`-only-implementing types will primarily be -used for localised FFI, neither is expected to be so pervasive throughout Rust -software to the extent that all existing `Sized` or `?Sized` bounds would need to -be immediately reconsidered in light of their addition. If a user of a -up-to-`RuntimeSized`-implementing type or a `Pointee`-only-implementing type did +`RuntimeSized` types will primarily be used for localised performance optimisation, +and `Pointee` types will primarily be used for localised FFI, neither is expected +to be so pervasive throughout Rust codebases to the extent that all existing +`Sized` or `?Sized` bounds would need to be immediately reconsidered in light +of their addition. If a user of a `RuntimeSized` type or a `Pointee` type did encounter a bound that needed to be relaxed, this could be changed in a patch to -the relevant crate without breaking backwards compatibility as-and-when such bounds -are discovered. +the relevant crate without breaking backwards compatibility as-and-when such +cases are encountered. ## Changes to the standard library [changes-to-the-standard-library]: #changes-to-the-standard-library @@ -416,13 +547,17 @@ With these new traits and having established changes to existing bounds which can be made while preserving backwards compatibility, the following changes could be made to the standard library: +- [`std::mem::size_of`][api_size_of] and [`std::mem::align_of`][api_align_of] + - `T: Sized` becomes `T: RuntimeSized` + - As described previously, `RuntimeSized` is a superset of `Sized` so + this change does not break any existing callers. - [`std::mem::size_of_val`][api_size_of_val] and [`std::mem::align_of_val`][api_align_of_val] - - `T: ?Sized` becomes `T: DynSized` - - As described previously, `?Sized` is equivalent to `DynSized` due to the + - `T: ?Sized` becomes `T: DynRuntimeSized` + - As described previously, `?Sized` is equivalent to `DynRuntimeSized` due to the existence of `size_of_val`, therefore this change does not break any existing callers. - - `DynSized` would not be implemented by `extern type`s from [rfcs#1861] + - `DynRuntimeSized` would not be implemented by `extern type`s from [rfcs#1861] [rfc_extern_types], which would prevent these functions from being invoked on types which have no known size or alignment. - [`std::clone::Clone`][api_clone] @@ -436,7 +571,7 @@ could be made to the standard library: this allows scalable vectors to be `Copy`, which is necessary and currently a blocker for that feature. - [`std::boxed::Box`][api_box] - - `T: ?Sized` becomes `T: DynSized` + - `T: ?Sized` becomes `T: DynRuntimeSized` - As before, this is not a breaking change and prevents types only implementing `Pointee` from being used with `Box`, as these types do not have the necessary size and alignment for allocation/deallocation. @@ -444,29 +579,16 @@ could be made to the standard library: As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard library would need to be reviewed and updated as appropriate. -## `RuntimeSized` and constness - -`RuntimeSized` is defined as types whose size is only known at runtime, not at -compilation time (or put otherwise, that the type's size is a function of its -runtime environment, amongst other things). In a `const` context, the runtime -environment used when determining the size of a up-to-`RuntimeSized` type is -the runtime environment of the host. This allows -[`std::mem::size_of_val`][api_size_of_val] to be made a `const` function. - -For example, if the size of a up-to-`RuntimeSized` register were determined by -checking the value of a register, then that register would be checked on the host -during constant evaluation. This is largely a theoretical concern as it is not -likely to be possible to get values of the up-to-`RuntimeSized` types in a -`const` context. - -Alternatives to this are discussed in the [Host runtime environment in `const` -contexts][host-runtime-environment-in-const-contexts] section. - # Drawbacks [drawbacks]: #drawbacks -This is a fairly large change to the language given how fundamental the `Sized` -trait is, so it could be deemed too confusing to introduce more complexity. +- This is a fairly significant change to the `Sized` trait, which has been in + the language since 1.0 and is now well-understood. +- This RFC's proposal that adding a bound of `RuntimeSized`, `DynSized`, + `DynRuntimeSized` or `Pointee` would remove the default `Sized` bound is + somewhat unintuitive. Typically adding a trait bound does not remove another + trait bound, however it's debatable whether this is more or less confusing + than existing `?Sized` bounds. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -474,13 +596,13 @@ trait is, so it could be deemed too confusing to introduce more complexity. There are various points of difference to the [prior art](#prior-art) related to `Sized`, which spans almost the entirety of the design space: -- In contrast to [rfcs#709][rfc_truly_unsized_types], marker types aren't used +- In contrast to [rfcs#709][rfc_truly_unsized_types], marker *types* aren't used to disable `Sized` because we are able to introduce traits to do so without backwards compatibility hazards and that feels more appropriate for Rust. -- In contrast to [rfcs#1524][rfc_custom_dst], the `Sized` trait isn't extended - as this wouldn't be sufficient to capture the range in sizedness that this RFC - aims to capture (i.e. for scalable vectors with `RuntimeSized` or `extern type`s - with `Pointee`), even if it theoretically could enable Custom DSTs. +- In contrast to [rfcs#1524][rfc_custom_dst], items are not added to the `Sized` + trait as this wouldn't be sufficient to capture the range in sizedness that this + RFC aims to capture (i.e. for scalable vectors with `RuntimeSized` or + `extern type`s with `Pointee`), even if it theoretically could enable Custom DSTs. - In contrast to [rfcs#1993][rfc_opaque_data_structs], [rust#44469][pr_dynsized], [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], @@ -497,6 +619,12 @@ There are various points of difference to the [prior art](#prior-art) related to traits. Custom DSTs are still compatible with this proposal using a `Contiguous` trait as in [rfcs#2594][rfc_custom_dst_electric_boogaloo]. +There are some alternatives to the decisions made in this RFC: + +- Instead of automatically upgrading `RuntimeSized` and `DynRuntimeSized` bounds + in `const` contexts, change it to these bounds permanently and add a new function + which cannot be `const` which accepts `RuntimeSized` and `DynRuntimeSized` types. + ## Bikeshedding [bikeshedding]: #bikeshedding @@ -508,7 +636,7 @@ ultimately need to be decided but aren't the important part of the RFC. It may seem that the `Pointee` trait is unnecessary as this is equivalent to the absense of any bounds whatsoever, but having an `Pointee` trait is necessary to -enable the meaning of `?Sized` to be re-defined to be equivalent to `DynSized` +enable the meaning of `?Sized` to be re-defined to be equivalent to `DynRuntimeSized` and avoid complicated behaviour change over an edition. Without `Pointee`, if a user wanted to remove all sizedness bounds from a generic @@ -517,84 +645,39 @@ parameter then they would have two options: 1. Introduce new relaxed bounds (i.e. `?DynSized`), which has been found unacceptable in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] summarizes these discussions) -2. Maintain `?Sized`'s existing meaning of removing the implicit `Sized` bound - - This could be maintained with or without having the existence of bounds - for `RuntimeSized` and `DynSized` remove the implicit `Sized` bound. +2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound The latter is the only viable option, but this would complicate changing -`size_of_val`'s existing `?Sized` bound. - -`?Sized` can be redefined to be equivalent to `DynSized` in all editions -and the syntax can be removed in a future edition only because adding an -`Pointee` bound is equivalent to removing the `Sized` bound and imposing -no constraints on the sizedness of a parameter. - -Without `Pointee`, a complicated migration would be necessary to change -all current uses of `?Sized` to `DynSized` (as these are equivalent) and -then future uses of `?Sized` would now accept more types than `?Sized` -previously did (they would now accept the `extern type`s). - -In [rfcs#3396][rfc_extern_types_v2], `MetaSized` was introduced and used a -similar mechanism over an edition to redefine `?Sized`. - -## Host runtime environment in `const` contexts -[host-runtime-environment-in-const-contexts]: #host-runtime-environment-in-const-contexts - -As previously described, the runtime environment of a up-to-`RuntimeSized` -type in `const` context is defined to be the host's runtime environment. - -However, this is not the only possible solution - using -[`std::mem::size_of_val`][api_size_of_val] as an example: - -| `size_of_val` | Runtime Context | `const` Context | -| -------------- | ------------------------------------------------ | ------------------- | -| `Sized` | Trivially known | Trivially known | -| `RuntimeSized` | From runtime environment (i.e. check a register) | ??? | -| `DynSized` | Computed from value | Computed from value | - -- Do not allow `size_of_val` to be a `const fn` - - This would prevent any function which may need `size_of_val` from - being `const` (and any function which depends on that function, etc etc). -- Define a `size_of_val_runtime` and `size_of_val_const` - - It would be impossible to define a function which accepted both `Sized` - and `DynSized` but not `RuntimeSized` without having to reorder - the proposed hierarchy (which has many complications of its own). -- Try to find an alternative hierarchy of traits - - Instead of having a linear hierarchy, have a diamond-shaped hierarchy or - something else - this adds significantly more complexity to the proposal. -- Define the runtime environment for `RuntimeSized` in a `const` context - -Of the potential alternatives, defining the runtime environment to be the host -in `const` contexts has the least complexity, but it is not immediately clear -that it is a feasible solution. - -| `size_of_val` | Runtime Context | `const` Context | -| -------------- | ---------------------------------- | --------------------------------- | -| `Sized` | Trivially known | Trivially known | -| `RuntimeSized` | From runtime environment of target | From runtime environment of host | -| `DynSized` | Computed from value | Computed from value | - -This solution necessitates that the requirements of a given type of its runtime -environment (for example, that it be a specific target that has a register containing -its size) are also satisfied by the host. If a value of one of these types exists in -a `const` context to be provided to `size_of_val` then there must exist `const` functions -with the appropriate `#[target_feature]` and/or `#[cfg]` attributes to create it and -therefore the host must satisfy the type's requirements (and the implementation of the -`size_of_val` intrinsic in the interpreter has likely been updated to know how to -determine the size). - -In practice, there likely are not appropriate `const` functions which would enable -values of these types to exist in a `const` context, so this is a largely theoretical -concern (while still allowing `size_of_val` to be defined with just a `DynSized` bound). - -## Alternatives to this RFC +`size_of_val`'s existing `?Sized` bound: + +Without `Pointee`, `?Sized` would be equivalent to `DynRuntimeSized` until +`extern type`s are stabilised (e.g. a `?Sized` bound would accept exactly the +same types as a `DynRuntimeSized` bound, but after `extern type`s are introduced, +`?Sized` bounds would accept `extern type`s and `DynRuntimeSized` bounds would not). +`extern type`s would need to be introduced over an edition and all existing `?Sized` +bounds rewritten to `?Sized + DynRuntimeSized`. This is the same mechanism described +in [rfcs#3396][rfc_extern_types_v2] to introduce it's `MetaSized` trait. + +## Why have a non-linear hierarchy? +[why-have-a-non-linear-hierarchy]: #why-have-a-non-linear-hierarchy + +Previous iterations of this RFC proposed `RuntimeSized: DynSized` and +`DynSized: Pointee` rather than having the diamond hierarchy that the RFC +currently has - but this was found to be unsuitable as `size_of_val` had +to have a `DynSized` bound, which meant it could also accept `RuntimeSized` +types, and this caused issues if `size_of_val` were to be made `const`. + +It may be possible to go back to a linear hierarchy of traits if the +RFC's current proposal upgrading of bounds in `const` contexts is kept. + +## Alternatives to this accepting this RFC [alternatives-to-this-rfc]: #alternatives-to-this-rfc There are not many alternatives to this RFC to unblock `extern type`s and scalable vectors: - Without this RFC, scalable vectors from [rfcs#3268][rfc_scalable_vectors] - would remain blocked unless special-cased by the compiler to bypass the type + would remain blocked unless special-cased by the compiler in the type system. - Extern types from [rfcs#1861][rfc_extern_types] would remain blocked if no action was taken, unless: @@ -720,8 +803,8 @@ the `Sized` trait, summarised below: - It was argued that `?Trait`s are powerful and should be made more ergonomic rather than avoided. - [kennytm][author_kennytm] left a useful comment summarising [which - standard library bounds would benefit from relaxation to a `DynSized` bound] - (https://github.com/rust-lang/rust/pull/46108#issuecomment-353672604). + standard library bounds would benefit from relaxation to a `DynSized` + bound](https://github.com/rust-lang/rust/pull/46108#issuecomment-353672604). - Ultimately this was closed [after a language team meeting](https://github.com/rust-lang/rust/pull/46108#issuecomment-360903211) deciding that `?DynSized` was ultimately too complex and couldn't be justified by support for a relatively niche feature like `extern type`. @@ -767,14 +850,14 @@ the `Sized` trait, summarised below: - The proposed `DynSized` trait in [rfcs#2310][rfc_dynsized_without_dynsized] is really quite similar to the trait proposed by this RFC except: - It includes an `#[assume_dyn_sized]` attribute to be added to - `T: ?Sized` bounds instead of replacing them with `T: DynSized`, which - would warn instead of error when a non-`DynSized` implementing type is + `T: ?Sized` bounds instead of replacing them with `T: DynRuntimeSized`, + which would warn instead of error when a non-`DynRuntimeSized` type is substituted into `T`. - This is to avoid a backwards compatibility break for uses of `size_of_val` and `align_of_val` with `extern type`s, but it is unclear why this is necessary given that `extern type`s are unstable. - - It does not include `RuntimeSized` or `Pointee`. + - It does not include `RuntimeSized`, `DynRuntimeSized` or `Pointee`. - Adding an explicit bound for `DynSized` does not remove the implicit bound for `Sized`. - [rust#49708: `extern type` cannot support `size_of_val` and `align_of_val`][issue_extern_types_align_size], [joshtriplett][author_joshtriplett], Apr 2018 @@ -865,9 +948,6 @@ the `Sized` trait, summarised below: - Automatically implemented for all types with an alignment (includes all `Sized` types). - `Aligned` is a supertrait of `Sized`. - - It's not immediately obvious how `Aligned` would work with this RFC's proposed - traits - would `Aligned` still be a supertrait of `Sized` or of `Pointee`? would - you need a `SizedAligned`, `RuntimeAligned`, `DynAligned` and `Aligned`? - [rfcs#3396: Extern types v2][rfc_extern_types_v2], [Skepfyr][author_skepfyr], Feb 2023 - Proposes a `MetaSized` trait for types whose size and alignment can be determined solely from pointer metadata without having to dereference the @@ -879,9 +959,9 @@ the `Sized` trait, summarised below: - `MetaSized` types can be the last field of a struct as the offset can be determined from the pointer metadata alone. - `Sized` is not a supertrait or subtrait of `MetaSized`. - - This may make the proposal subject to the backwards - incompatibilities described in - [Changes to the Standard Library](#changes-to-the-standard-library). + - This may make the proposal subject to backwards + incompatibilities described in [Auto traits and backwards + compatibility][auto-traits-and-backwards-compatibility]. - `size_of_val`'s bound would be changed to `T: ?Sized + MetaSized`. - Attempts to sidestep backwards compatibility issues with introducing a default bound via changing what `?Sized` means across an edition boundary. @@ -912,22 +992,22 @@ below but not summarized: - [pre-RFC: unsized types][rfc_unsized_types], [japaric][author_japaric], Mar 2016 There haven't been any particular proposals which have included an equivalent -of the `RuntimeSized` trait, as the scalable vector types proposal in [RFC 3268] -[rfc_scalable_vectors] is relatively newer and less well known: +of the `RuntimeSized` and `DynRuntimeSized` traits, as the scalable vector types +proposal in [RFC 3268][rfc_scalable_vectors] is relatively newer and less well known: - [rfcs#3268: Add scalable representation to allow support for scalable vectors][rfc_scalable_vectors], [JamieCunliffe][author_jamiecunliffe], May 2022 - Proposes temporarily special-casing scalable vector types to be able to implement `Copy` without implementing `Sized` and allows function return values to be `Copy` or `Sized` (not just `Sized`). - Neither of these changes would be necessary with this RFC, scalable - vectors would just implement `RuntimeSized` and function return values would + vectors would just be `RuntimeSized` types and function return values would just need to implement `RuntimeSized`, not `Sized`. To summarise the above exhaustive listing of prior art: -- No previous works have proposed an equivalent of a `RuntimeSized` trait or - a `Pointee` trait (with the exception of[Sized, DynSized, and Unsized][blog_dynsized_unsized] - for `Pointee`), only `DynSized`. +- No previous works have proposed an equivalent of a `RuntimeSized`, + `DynRuntimeSized` or `Pointee` trait ([Sized, DynSized, and + Unsized][blog_dynsized_unsized] is closest), only `DynSized`. - One proposal proposed adding a marker type that as a field would result in the containing type no longer implementing `Sized`. - Often proposals focused at Custom DSTs preferred to combine the @@ -970,7 +1050,8 @@ this proposal: the presence of other size trait bounds. - [Sized, DynSized, and Unsized][blog_dynsized_unsized] is very similar and a major inspiration for this proposal. It has everything this proposal has except - for `RuntimeSized` and all the additional context an RFC needs. + for `RuntimeSized` and `DynRuntimeSized` and all the additional context an + RFC needs. # Unresolved questions [unresolved-questions]: #unresolved-questions @@ -989,14 +1070,12 @@ this proposal: as a follow-up as it is more related to `extern type` than extending `Sized` anyway. Implementation of this RFC would not create any `Pointee` types. - How would this interact with proposals to split alignment and sizedness into - separate traits? + separate traits, like [rfcs#3319][rfc_aligned]? - Some prior art had different rules for automatically implementing `DynSized` on structs/enums vs on unions - are those necessary for these traits? -- `Pointee` is already the name of an unstable trait, so it may make sense to rename - this to something else, but it seemed better than `Unsized` as it was named - in earlier drafts. - - Could the existing `std::ptr::Pointee` trait be used as the supertrait - of all other sizedness traits? +- Could the existing `std::ptr::Pointee` trait be used as the supertrait + of all other sizedness traits? + - Otherwise `Pointee` as proposed would need renamed. - How serious are the limitations on changing the bounds of associated types? # Future possibilities @@ -1004,14 +1083,11 @@ this proposal: - Additional size traits could be added as supertraits of `Sized` if there are other delineations in sized-ness that make sense to be drawn. - - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2], something like - `Sized: MetaSized`, `MetaSized: RuntimeSized`, `RuntimeSized: DynSized` and - `DynSized: Pointee`. + - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2] could be added between + `Sized` and `DynSized` - The requirement that users cannot implement any of these traits could be relaxed in future to support custom DSTs or any other proposed feature which required it. -- Relax prohibition of `Pointee` fields in some limited contexts, for example if - it is the final field and the offset of it is never computed. - Depending on a trait which has one of the proposed traits as a supertrait could imply a bound of the proposed trait, enabling the removal of boilerplate. - Consider allowing associated type bounds to be relaxed over an edition. @@ -1027,9 +1103,11 @@ this proposal: you'd want to add `Pointee` as a supertrait of `DynSized` with some non-`()` metadata type, or something along those lines. +[api_align_of]: https://doc.rust-lang.org/std/mem/fn.align_of.html [api_align_of_val]: https://doc.rust-lang.org/std/mem/fn.align_of_val.html [api_box]: https://doc.rust-lang.org/std/boxed/struct.Box.html [api_clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html +[api_size_of]: https://doc.rust-lang.org/std/mem/fn.size_of.html [api_size_of_val]: https://doc.rust-lang.org/std/mem/fn.size_of_val.html [author_aturon]: https://github.com/aturon [author_cad97]: https://github.com/CAD97 From 592d19291c679fc780f1944f6bcc1a2e53957b25 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 21 Oct 2024 16:31:34 +0100 Subject: [PATCH 06/45] using const traits --- text/0000-sized-hierarchy.md | 1031 ++++++++++++++++++---------------- 1 file changed, 557 insertions(+), 474 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index a6b181457bb..4e6467f304b 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -15,18 +15,18 @@ whose size can never be known. Supporting the former is a prerequisite to stable scalable vector types and supporting the latter is a prerequisite to unblocking extern types. -*The ideas in this RFC are heavily inspired by and borrow from blog posts and -previous RFCs which have proposed similar changes to the language, see -[Prior Art](#prior-art) for a full list with appropriate attributions.* +This RFC necessarily depends on the experimental feature +[`const Trait`][goal_const_traits] and is written assuming familiarity with that +feature. # Background [background]: #background -Rust has the `Sized` marker trait which indicates that a type's size is -statically known at compilation time. `Sized` is an trait which is automatically -implemented by the compiler on any type that has a statically known size. All -type parameters have a default bound of `Sized` and `?Sized` syntax can be used -to remove this bound. +Rust has the [`Sized`][api_sized] marker trait which indicates that a type's size +is statically known at compilation time. `Sized` is an trait which is +automatically implemented by the compiler on any type that has a statically known +size. All type parameters have a default bound of `Sized` and `?Sized` syntax can +be used to remove this bound. There are two functions in the standard library which can be used to get a size, [`std::mem::size_of`][api_size_of] and [`std::mem::size_of_val`][api_size_of_val]: @@ -66,20 +66,25 @@ cannot be known statically at compilation time and must be computed at runtime. Within this RFC, no terminology is introduced to describe all types which do not implement `Sized` in the same sense as "unsized" is colloquially used. -Furthermore, throughout the RFC, the terminology "`Trait` types" will be used -to refer to those types which implement `Trait` and all of its supertraits but -none of its subtraits. For example, a `DynSized` type would be a type which -implements `DynSized`, `DynRuntimeSized` and `Pointee`, but not `Sized`. -`[usize]` would be referred to as a "`DynSized` type". +Throughout the RFC, the following terminology will be used: + +- "`Trait` types" will be used to refer to those types which implement `Trait` + and all of its supertraits but none of its subtraits. For example, a `DynSized` + type would be a type which implements `DynSized`, and `Pointee`, but not + `Sized`. `[usize]` would be referred to as a "`DynSized` type". +- "Runtime-sized" types will be used those types whose size is statically known + but only at runtime. These would include the scalable vector types mentioned + in the motivation below, or those that implement `Sized` but not `const Sized` + in the RFC. +- The bounds on the generic parameters of a function may be referred to simply + as the bounds on the function (e.g. "the caller's bounds"). -Throughout the RFC, the bounds on the generic parameters of a function may be -referred to simply as the bounds on the function (e.g. "the caller's bounds"). # Motivation [motivation]: #motivation -Introducing a hierarchy of `Sized` traits resolves blockers for other RFCs which -have had significant interest. +Introducing a hierarchy of `Sized` and elaborating on the implications of constness +on `Sized` traits resolves blockers for other RFCs which have had significant interest. ## Runtime-sized types and scalable vector types [runtime-sized-types-and-scalable-vector-types]: #runtime-sized-types-and-scalable-vector-types @@ -104,10 +109,10 @@ returned from functions, can be variables on the stack, etc. These types should implement `Copy` but given that `Copy` is a supertrait of `Sized`, they cannot be `Copy` without being `Sized`, and aren't `Sized`. -Introducing a hierarchy of `Sized` traits will enable `Copy` to be a supertrait -of the trait for types whose size is known statically at runtime, and therefore -enable these types to be `Copy` and function correctly without special cases in -the type system. See [rfcs#3268: Scalable Vectors][rfc_scalable_vectors]. +Introducing a `const Sized` trait will enable `Copy` to be implemented for +those types whose size is known statically only at runtime, function correctly +without special cases in the type system. See +[rfcs#3268: Scalable Vectors][rfc_scalable_vectors]. ## Unsized types and extern types [unsized-types-and-extern-types]: #unsized-types-and-extern-types @@ -125,13 +130,14 @@ if called with an extern type, but this cannot be expressed in the bounds of `size_of_val` and `align_of_val` and this remains a blocker for extern types. Furthermore, unsized types cannot be members of structs as their alignment is -unknown and this is necessary to calculate field offsets. `extern type`s also +unknown and this is necessary to calculate field offsets. Extern types also cannot be used in `Box` as `Box` requires size and alignment for both allocation and deallocation. -Introducing a hierarchy of `Sized` traits will enable extern types to implement -a trait for unsized types and therefore not meet the bounds of `size_of_val` -and `align_of_val`. +Introducing a hierarchy of `Sized` traits will enable the backwards-compatible +introduction of a trait which only extern types do not implement and will +therefore enable the bounds of `size_of_val` and `align_of_val` to disallow +instantiations with extern types. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -147,186 +153,170 @@ Various parts of Rust depend on knowledge of the size of a type to work, for example: - [`std::mem::size_of_val`][api_size_of_val] computes the size of a value, - and thus cannot accept `extern type`s which have no size, and this should + and thus cannot accept extern types which have no size, and this should be prevented by the type system. - Rust allows dynamically-sized types to be used as struct fields, but the - alignment of the type must be known, which is not the case for `extern type`s. + alignment of the type must be known, which is not the case for extern types. - Allocation and deallocation of an object with `Box` requires knowledge of - its size and alignment, which `extern type`s do not have. + its size and alignment, which extern types do not have. - For a value type to be allocated on the stack, it needs to have statically known size, which dynamically-sized and unsized types do not have (but sized and "runtime sized" types do). Rust uses marker traits to indicate the necessary knowledge required to know -the size of a type, if it can be known. There are five traits related to the size -of a type in Rust: `Sized`, `RuntimeSized`, `DynSized`, `RuntimeDynSized` and -`Pointer`. - -*There is already an unstable trait named `Pointee`, this isn't intended to replace -that trait and either trait may need to be renamed so as not to overlap, but -`Pointee` made sense as a placeholder name for this.* +the size of a type, if it can be known. There are three traits related to the size +of a type in Rust: `Sized`, `DynSized`, and the existing unstable +`std::ptr::Pointee`. Each of these traits can be implemented as `const` when the +size is knowable at compilation time. -`Sized` is a supertrait of both `RuntimeSized` and `DynSized`, so every type of -which implements `Sized` also implements `RuntimeSized` and `DynSized`. -`RuntimeSized` and `DynSized` are both supertraits of `DynRuntimeSized` and -`DynRuntimeSized` is a supertrait of `Pointee`. +`Sized` is a supertrait of `DynSized`, so every type which implements `Sized` +also implements `DynSized`. Likewise, `DynSized` is a supertrait of `Pointee`. +`Sized` is `const` if-and-only-if `DynSized` is `const`, and `DynSized` is +`const` if-and-only-if `Pointee` is `const`. ``` -┌───────────────────────────────────────────────────────────────┐ -│ ┌───────────────────────────────────────────────────────────┐ │ -│ │ ┌──────────────────────────────┐┌───────────────────────┐ │ │ -│ │ │ ┌────────────────────────────┴┴─────────────────────┐ │ │ │ -│ │ │ │ Sized │ │ │ │ -│ │ │ │ {type, target} │ │ │ │ -│ │ │ └────────────────────────────┬┬─────────────────────┘ │ │ │ -│ │ │ RuntimeSized ││ DynSized │ │ │ -│ │ │ {type, target, runtime env} ││ {type, target, value} │ │ │ -│ │ └──────────────────────────────┘└───────────────────────┘ │ │ -│ │ DynRuntimeSized │ │ -│ │ {type, target, runtime env, value} │ │ -│ └───────────────────────────────────────────────────────────┘ │ -│ Pointee │ -│ {*} │ -└───────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────┐ +│ ┌──────────────────────────────────────────────────────────────┐ │ +│ │ ┌──────────────────────────────────────────────────────────┐ │ │ +│ │ │ ┌────────────────┐ │ │ │ +│ │ │ │ const Sized │ Sized │ │ │ +│ │ │ │ {type, target} │ {type, target, runtime env} │ │ │ +│ │ │ └────────────────┘ │ │ │ +│ │ └──────────────────────────────────────────────────────────┘ │ │ +│ │ ┌───────────────────────┐ │ │ +│ │ │ const DynSized │ DynSized │ │ +│ │ │ {type, target, value} │ {type, target, runtime env, value} │ │ +│ │ └───────────────────────┘ │ │ +│ └──────────────────────────────────────────────────────────────┘ │ +│ ┌───────────────────────┐ │ +│ │ const Pointee │ Pointee │ +│ │ {*} │ {*, runtime env} │ +│ └───────────────────────┘ │ +└──────────────────────────────────────────────────────────────────┘ ``` -`Sized` is implemented on types which require knowledge of only the type and -target platform in order to compute their size. For example, `usize` implements -`Sized` as knowing only the type is `usize` and the target is -`aarch64-unknown-linux-gnu` then we can know the size is eight bytes, and likewise -with `armv7-unknown-linux-gnueabi` and a size of four bytes. - -`DynSized` requires more knowledge than `Sized` to compute the size: it may -additionally require a value (therefore `size_of` is not implemented for -`DynSized`, only `size_of_val`). For example, `[usize]` implements `DynSized` -as knowing the type and target is not sufficient, the number of elements in -the slice must also be known, which requires having the value. - -Like `DynSized`, `RuntimeSized` also requires more knowledge than `Sized` to -compute the size: it may additionally require knowledge of the "runtime -environment". For example, `svint8_t` is a scalable vector type (from +`const Sized` is implemented on types which require knowledge of only the +type and target platform in order to compute their size. For example, `usize` +implements `const Sized` as knowing only the type is `usize` and the target is +`aarch64-unknown-linux-gnu` then we can know the size is eight bytes, and +likewise with `armv7-unknown-linux-gnueabi` and a size of four bytes. + +Some types' size can be computed using only knowledge of the type and target +platform, but only at runtime, and these types implement `Sized` (notably, +not `const`). For example, `svint8_t` is a scalable vector type (from [rfc#3268][rfc_scalable_vectors]) whose length depends on the specific implementation of the target (i.e. it may be 128 bits on one processor and -256 bits on another). `RuntimeSized`-types can be used with `size_of` and -`size_of_val` (as a value is not required), but cannot be used in a `const` -context. +256 bits on another). -`DynRuntimeSized` combines the requirements from `DynSized` and -`RuntimeSized` - these types may need a value and knowledge of the type, -target, and runtime environment. For example, `[svint8_t]` requires a value -to know how many elements there are, and then information from the runtime -environment to know the size of a `svint8_t`. +`const DynSized` requires more knowledge than `const Sized` to compute the size: +it may additionally require a value (therefore `size_of` is not implemented for +`DynSized`, only `size_of_val`). For example, `[usize]` implements +`const DynSized` as knowing the type and target is not sufficient, the number of +elements in the slice must also be known, which requires having the value. + +As `Sized` is to `const Sized`, `DynSized` is to `const DynSized`: `DynSized` +requires a value, knowledge of the type and target platform, and can only be +computed at runtime. For example, `[svint8_t]` requires a value to know how +many elements there are, and then information from the runtime environment +to know the size of a `svint8_t`. `Pointee` is implemented by any type that can be used behind a pointer, which is to say, every type (put otherwise, these types may or may not be sized at all). For example, `Pointee` is therefore implemented on a `u32` which is trivially sized, a `[usize]` which is dynamically sized, a `svint8_t` which is runtime sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which has no -known size. - -All type parameters have an implicit bound of `Sized` which will be automatically -removed if a `RuntimeSized`, `DynSized`, `DynRuntimeSized` or `Pointee` bound is -present instead. +known size. `Pointee` is implemented as `const` when knowledge of the runtime +environment is not required (e.g. `const Pointee` for `u32` and `[usize]` but +bare `Pointee` for `svint8_t` or `[svint8_t]`). -Prior to the introduction of `RuntimeSized`, `DynSized`, `DynRuntimeSized` and -`Pointee`, `Sized`'s implicit bound could be removed using the `?Sized` syntax, -which is now equivalent to a `DynRuntimeSized` bound and will be deprecated in -the next edition. +All type parameters have an implicit bound of `const Sized` which will be +automatically removed if a `Sized`, `const DynSized`, `DynSized`, `const Pointee` +or `Pointee` bound is present instead. -As `RuntimeSized` and `DynRuntimeSized` types may require knowledge of the runtime -environment for their size to be computed, they cannot be used in `const` contexts. -`const` functions with a `RuntimeSized` or `DynRuntimeSized` bound will have those -bounds upgraded to `Sized` and `DynSized` bounds respectively when called from a -`const` context. +Prior to the introduction of `DynSized` and `Pointee`, `Sized`'s implicit bound +(now a `const Sized` implicit bound) could be removed using the `?Sized` syntax, +which is now equivalent to a `DynSized` bound in non-`const fn`s and +`~const DynSized` in `const fn`s and will be deprecated in the next edition. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Introduce four new marker traits, `RuntimeSized`, `DynSized`, `DynRuntimeSized` -and `Pointee`, creating a "non-linear hierarchy" of `Sized` traits: +Introduce a new marker trait, `DynSized`, adding it to a trait hierarchy with the +[`Sized`][api_sized] and [`Pointee`][api_pointee] traits, and make all sizedness +traits `const`: ``` - ┌────────────────┐ - │ Sized │ - │ {type, target} │ - └────────────────┘ - │ - ┌──────────────┴─────────────┐ - │ │ -┌─────────────┴───────────────┐┌───────────┴───────────┐ -│ RuntimeSized ││ DynSized │ -│ {type, target, runtime env} ││ {type, target, value} │ -└─────────────────────────────┘└───────────────────────┘ - │ │ - └──────────────┬─────────────┘ - │ - ┌──────────────────┴─────────────────┐ - │ DynRuntimeSized │ - │ {type, target, runtime env, value} │ - └────────────────────────────────────┘ - │ - ┌────┴────┐ - │ Pointee │ - │ {*} │ - └─────────┘ + ┌────────────────┐ + │ const Sized │ + │ {type, target} │ + └────────────────┘ + │ +┌───────────┴───────────┐ +│ const DynSized │ +│ {type, target, value} │ +└───────────────────────┘ + │ + ┌───────┴───────┐ + │ const Pointee │ + │ {*} │ + └───────────────┘ ``` Or, in Rust syntax: ```rust= -trait Sized: RuntimeSized + DynSized {} +const trait Sized: ~const DynSized {} -trait RuntimeSized: DynRuntimeSized {} - -trait DynSized: DynRuntimeSized {} +const trait DynSized: ~const std::ptr::Pointee {} +``` -trait DynRuntimeSized: Pointee {} +`Pointee` was specified in [rfcs#2580][rfc_pointer_metadata_vtable] and is +currently unstable. `Pointee` would become a `const` trait. It could be moved +to `std::marker` alongside the other sizedness traits or kept in `std::ptr`. -trait Pointee {} -``` +## Implementing `Sized` +[implementing-sized]: #implementing-sized -Like `Sized`, implementations of the four proposed traits are automatically -generated by the compiler and cannot be implemented manually: +Implementations of the proposed traits are automatically generated by +the compiler and cannot be implemented manually: -- `Pointee` +- [`Pointee`][api_pointee] - Types that which can be used from behind a pointer (they may or may not have a size). - `Pointee` will be implemented for: - - `DynRuntimeSized` types - - `extern type`s from [rfcs#1861][rfc_extern_types] - - compound types where every element is `Pointee` - - In practice, every type will implement `Pointee`. -- `DynRuntimeSized` - - Types whose size is computable given a value, and knowledge of the - type, target and runtime environment. - - `DynRuntimeSized` is a subtrait of `Pointee` - - `DynRuntimeSized` will be implemented for: - `DynSized` types - - `RuntimeSized` types - - slices `[T]` where every element is `DynRuntimeSized` - - compound types where every element is `DynRuntimeSized` + - compound types where every element is `Pointee` + - `const Pointee` will be implemented for: + - `const DynSized` types + - `extern type`s from [rfcs#1861][rfc_extern_types] + - compound types where every element is `const Pointee` + - In practice, every type will implement `Pointee` (as in + [rfcs#2580][rfc_pointer_metadata_vtable]). - `DynSized` - Types whose size is computable given a value, and knowledge of the - type and target. - - `DynSized` is a subtrait of `DynRuntimeSized`. + type, target platform and runtime environment. + - `const DynSized` does not require knowledge of the runtime + environment + - `DynSized` is a subtrait of `Pointee` - `DynSized` will be implemented for: - `Sized` types - - slices `[T]` where every element is `Sized` + - slices `[T]` where every element is `DynSized` + - compound types where every element is `DynSized` + - `const DynSized` will be implemented for: + - `const Sized` types + - slices `[T]` where every element is `const Sized` - string slice `str` - trait objects `dyn Trait` - - compound types where every element is `DynSized` -- `RuntimeSized` - - Types whose size is computable given knowledge of the type, target and - runtime environment. - - `RuntimeSized` is a subtrait of `DynRuntimeSized`. - - `RuntimeSized` will be implemented for: - - `Sized` types - - scalable vectors from [rfcs#3268][rfc_scalable_vectors] - - compound types where every element is `RuntimeSized` + - compound types where every element is `const DynSized` - `Sized` - - Types whose size is computable given knowledge of the type and target. - - `Sized` is a subtrait of `RuntimeSized` and `DynSized`. + - Types whose size is computable given knowledge of the type, target + platform and runtime environment. + - `const Sized` does not require knowledge of the runtime environment + - `Sized` is a subtrait of `DynSized`. - `Sized` will be implemented for: + - scalable vectors from [rfcs#3268][rfc_scalable_vectors] + - compound types where every element is `Sized` + - `const Sized` will be implemented for: - primitives `iN`, `uN`, `fN`, `char`, `bool` - pointers `*const T`, `*mut T` - function pointers `fn(T, U) -> V` @@ -334,34 +324,214 @@ generated by the compiler and cannot be implemented manually: - never type `!` - unit tuple `()` - closures and generators - - compound types where every element is `Sized` + - compound types where every element is `const Sized` - anything else which currently implements `Sized` -In the compiler, `?Sized` would be made syntactic sugar for a `DynRuntimeSized` -bound (and eventually removed in an upcoming edition). A `DynRuntimeSized` -bound is equivalent to a `?Sized` bound as all values in Rust today whose types -do not implement `Sized` are valid arguments to `std::mem::size_of_val` and as -such have a size which can be computed at runtime, and therefore will implement -`DynRuntimeSized`. As there are currently no `extern type`s or other types which -would not implement `DynRuntimeSized`, every type in Rust today which would -satisfy a `?Sized` bound would satisfy a `DynRuntimeSized` bound. +Introducing new automatically implemented traits is backwards-incompatible, +at least if you try to add it as a bound to an existing function[^1][^2] (and +new auto traits that which go unused aren't that useful), but due to being +supertraits of `Sized` and `Sized` being a default bound, these +backwards-incompatibilities are avoided for `DynSized` and `Pointee`. + +Relaxing a bound from `Sized` to `DynSized` or `Pointee` is non-breaking as +the calling bound must have either `T: Sized` or `T: ?Sized`, both of which +would satisfy any relaxed bound[^3]. + +However, it would still be backwards-incompatible to relax the `Sized` bound on +a trait's associated type[^4] for the proposed traits. + +[^1]: Adding a new automatically implemented trait and adding it as a bound to + an existing function is backwards-incompatible with generic functions. Even + though all types could implement the trait, existing generic functions will be + missing the bound. + + If `Foo` were introduced to the standard library and implemented on every + type, and it was added as a bound to `size_of` (or any other generic + parameter).. + + ```rust= + auto trait Foo; + + fn size_of() { /* .. */ } // `Foo` bound is new! + ``` + + ...then user code would break: + + ```rust= + fn do_stuff(value: T) { size_of(value) } + // error! the trait bound `T: Foo` is not satisfied + ``` +[^2]: Trait objects passed by callers would not imply the new trait. + + If `Foo` were introduced to the standard library and implemented on every + type, and it was added as a bound to `size_of_val` (or any other generic + parameter).. + + ```rust= + auto trait Foo; + + fn size_of_val(x: val) { /* .. */ } // `Foo` bound is new! + ``` + + ...then user could would break: + + ```rust + fn do_stuff(value: Box) { size_of_val(value) } + // error! the trait bound `dyn Display: Foo` is not satisfied in `Box` + ``` +[^3]: Callers of existing APIs will have one of the following `Sized` bounds: + + | Before ed. migration | After ed. migration | + | --------------------------------- | ------------------- | + | `T: Sized` (implicit or explicit) | `T: const Sized` | + | `T: ?Sized` | `T: const DynSized` | + + Any existing function in the standard library with a `T: Sized` bound + could be changed to one of the following bounds and remain compatible with + any callers that currently exist (as per the above table): + + | | `const Sized` | `Sized` | `const DynSized` | `DynSized` | `const Pointee` | `Pointee` + | -------------- | ------------- | ------- | ---------------- | ---------- | --------------- | --------- + | `const Sized` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ + + Likewise with a `T: ?Sized` bound: + + | | `const DynSized` | `DynSized` | `const Pointee` | `Pointee` + | ---------------- | ---------------- | ---------- | --------------- | --------- + | `const Sized` | ✔ | ✔ | ✔ | ✔ + | `const DynSized` | ✔ | ✔ | ✔ | ✔ +[^4]: Associated types of traits have default `Sized` bounds which cannot be + relaxed. For example, relaxing a `Sized` bound on `Add::Output` breaks + a function which takes a `T: Add` and passes `::Output` to + `size_of` as not all types which implement the relaxed bound will + implement `Sized`. + + If a default `Sized` bound on an associated trait, such as + `Add::Output`, were relaxed in the standard library... + + ```rust= + trait Add { + type Output: DynSized; + } + ``` + + ...then user code would break: + + ```rust= + fn do_stuff() -> usize { std::mem::size_of::<::Output>() } + //~^ error! the trait bound `::Output: Sized` is not satisfied + ``` + + Relaxing the bounds of an associated type is in effect giving existing + parameters a less restrictive bound which is not backwards compatible. + +## `Sized` bounds +[sized-bounds]: #sized-bounds + +`?Sized` would be made syntactic sugar for a `const DynSized` bound. A +`const DynSized` bound is equivalent to a `?Sized` bound as all values in Rust +today whose types do not implement `Sized` are valid arguments to +[`std::mem::size_of_val`][api_size_of_val] and as such have a size which can be +computed given a value and knowledge of the type and target platform, and +therefore will implement `const DynSized`. As there are currently no +extern types or other types which would not implement `const DynSized`, +every type in Rust today which would satisfy a `?Sized` bound would satisfy +a `const DynSized` bound. + +**Edition change:** In the current edition,`?Sized` will be syntatic sugar for +a `const DynSized` bound. In the next edition, use of `?Sized` syntax will be prohibited +over an edition and all uses of it will be rewritten to a `const DynSized` bound. + +A default implicit bound of `const Sized` is added by the compiler to every type +parameter `T` that does not have an explicit `Sized`, `?Sized`, `const DynSized`, +`DynSized`, `const Pointee` or `Pointee` bound. It is backwards compatible to change +the current implicit `Sized` bound to an `const Sized` bound as every type which +exists currently will implement `const Sized`. + +**Edition change:** In the current edition, all existing `Sized` bounds will be +sugar for `const Sized`. In the next edition, existing `Sized` bounds will be rewritten +to `const Sized` bounds and new bare `Sized` bounds will be non-const. + +An implicit `const DynSized` bound is added to the `Self` type of traits. Like +implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, `Sized`, +`DynSized` or `Pointee` bound is present. + +As`DynSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` +for these traits. + +## `size_of` and `size_of_val` +[size-of-and-size-of-val]: #size-of-and-size-of-val + +Runtime-sized types should be able to be passed to both `size_of` and `size_of_val`, +but only at runtime, which requires ensuring these functions are only const if their +arguments have const implementations of the relevant sizedness traits. + +[`size_of`][api_size_of] is a const-stable function since Rust 1.24.0 and currently +accepts a `T: Sized` bound. Therefore, even when used in a const context, `size_of` +could accept a runtime-sized type. It is therefore necessary to modify the bounds of +`size_of` to accept a `T: ~const Sized`, so that `size_of` is a const function +if-and-only-if `Sized` has a `const` implementation. + +```rust= +pub const fn size_of() -> usize { + /* .. */ +} +``` + +This has the potential to break existing code like `uses_size_of` in the below +example. However, due to the changes described in [`Sized` bounds][sized-bounds] +(changing the implicit `T: Sized` to `T: const Sized`, and treating explicit +`T: Sized` bounds as `T: const Sized` in the current edition), this code would +not break. + +```rust= +fn uses_size_of() -> usize { + const { std::mem::size_of() } +} +``` + +[`size_of_val`][api_size_of_val] is currently const-unstable, so its bound can be +changed from `?Sized` to `~const DynSized` without any backwards compatibility +issues. + +```rust= +pub const fn size_of_val(val: &T) -> usize +where + T: ~const DynSized, +{ + /* .. */ +} +``` -A default implicit bound of `Sized` is added by the compiler to every type -parameter `T` that does not have an explicit `Sized`, `?Sized`, `RuntimeSized`, -`DynSized`, `DynRuntimeSized` or `Pointee` bound. +While `DynSized` is equivalent to the current `?Sized` bound it replaces, it +excludes extern types (which `?Sized` by definition cannot), which prevents +`size_of_val` from being called with extern types from +[rfcs#1861][rfc_extern_types]. -An implicit `DynRuntimeSized` bound is added to the `Self` type of traits. Like -implicit `Sized` bounds, this is omitted if an explicit `Sized`, `RuntimeSized` -or `DynSized` bound is present. +These same changes apply to `align_of` and `align_of_val`. -`Pointee` types cannot be used in compound types (unless they are -`#[repr(transparent)]`) as the alignment of these types would need to be known -in order to calculate field offsets and this would not be possible. -`RuntimeSized`, `DynSized`, `DynRuntimeSized` or `Pointee` types could continue -to be used in compound types, but only as the last element. +## Implementing `Copy` for runtime-sized types -As `RuntimeSized`, `DynSized`, `RuntimeDynSized`, and `Pointee` are not default -bounds, there is no equivalent to `?Sized` for these traits. +Runtime-sized types are value types which should be able to be used as +locals, be copied, etc. To be able to implement [`Copy`][api_copy], these +types would need to implement [`Clone`][api_clone], and to implement +`Clone`, they need to implement `Sized`. + +This property trivially falls out of the previous proposals in this RFC: +runtime-sized types are `Sized` (but not `const Sized`) and therefore can +implement `Clone` and `Copy`. + +## Restrictions in compound types +[restrictions-on-compound-types]: #restrictions-on-compound-types + +`Pointee` types cannot be used in non-`#[repr(transparent)]` compound types +as the alignment of these types would need to be known in order to calculate field +offsets. `const Sized` types can be used in compound types with no restrictions. +`Sized`, `const DynSized` and `DynSized` types can be used in compound types, but +only as the last element. + +## Compiler performance implications +[compiler-performance-implications]: #compiler-performance-implications There is a potential performance impact within the trait system to adding supertraits to `Sized`, as implementation of these supertraits will need to be @@ -371,6 +541,9 @@ optimisation whereby `Sized`'s supertraits are assumed to be implemented and checking them is skipped - this should be sound as all of these traits are implemented by the compiler and therefore this property can be guaranteed. +## Forward compatibility with supertraits implying default bound removal +[forward-compatibility-with-supertraits-default-bound]: #forward-compatibility-with-supertraits-implying-default-bound-removal + Traits which are a supertrait of any of the proposed traits will not automatically imply the proposed trait in any bounds where the trait is used, e.g. @@ -382,213 +555,62 @@ struct NewRc {} // equiv to `T: NewTrait + Sized` as today ``` If the user wanted `T: DynSized` then it would need to be written explicitly. +This is forward compatible with trait bounds which have sizedness supertraits +implying the removal of the default `const Sized` bound. -## Edition changes -[edition-changes]: #edition-changes - -In the next edition, writing `?Sized` bounds would no longer be accepted and the -compiler would suggest users write `DynRuntimeSized` bounds instead. Existing -`?Sized` bounds will be trivially rewritten to `DynRuntimeSized` bounds by `rustfix`. - -## Constness -[constness]: #constness - -`RuntimeSized` and `DynRuntimeSized` types may need knowledge of the runtime -environment which prevents their use in a `const` context. - -In a `const` context, there is no runtime environment which can be used to -determine the size of these types. - -| [`size_of`][api_size_of] | Runtime Context | `const` Context | -| ------------------------ | ------------------------- | ------------------- | -| `Sized` | Trivially known | Trivially known | -| `RuntimeSized` | Check runtime environment | ❌ | - -| [`size_of_val`][api_size_of_val] | Runtime Context | `const` Context | -| -------------------------------- | ----------------------------------------------- | ------------------- | -| `Sized` | Trivially known | Trivially known | -| `RuntimeSized` | Check runtime environment | ❌ | -| `DynSized` | Computed from value | Computed from value | -| `DynRuntimeSized` | Computed from value + check runtime environment | ❌ | - -However, in a non-`const` context, `size_of` and `size_of_val` should be -callable with `RuntimeSized` types, and `size_of_val` should be callable with -`DynRuntimeSized` types. - -All `RuntimeSized` bounds will automatically be upgraded to `Sized` bounds when -called from a `const` context, and similarly `DynRuntimeSized` bounds will -automatically be upgraded to `DynSized` bounds when called from a `const` -context. - -It is not expected that this will be frequently encountered by users, mostly -as it will not be possible to obtain values of `RuntimeSized` types in -`const` contexts. - -## Auto traits and backwards compatibility -[auto-traits-and-backwards-compatibility]: #auto-traits-and-backwards-compatibility - -A hierarchy of `Sized` traits sidesteps [the backwards compatibility hazards -which typically scupper attempts to add new traits implemented on every -type][changing_rules_of_rust]. - -Adding a new auto trait to the bounds of an existing function would typically -be a breaking change, despite all types implementing the new auto trait, in -three cases: - -1. Callers would not have the new bound. For example, adding a new auto trait - (which is not a default bound) as a bound to `std_fn` would cause `user_fn` - to stop compiling as `user_fn`'s `T` would need the bound added too: - - ```rust - fn user_fn(value: T) { std_fn(value) } - fn std_fn(value: T) { /* .. */ } - //~^ ERROR the trait bound `T: NewAutoTrait` is not satisfied - ``` - - Unlike with an arbitrary new auto trait, the proposed traits are all - subtraits of `Sized` and every generic parameter already either has a - default bound of `Sized` or has a `?Sized` bound, which enables this risk - of backwards compatibility to be avoided. - - Relaxing the `Sized` bound of an existing function to `RuntimeSized`, - `DynSized`, `DynRuntimeSized` or `Pointee` would not break any callers, - as those callers must already have a `T: Sized` bound and therefore would - already satisfy the new relaxed bound. Callers may now have a stricter - bound than is necessary, but they likewise can relax their bounds without - that being a breaking change. - - If an existing function had a `?Sized` bound and this bound were changed to - `DynRuntimeSized` or relaxed to `Pointee`, then callers would either have a - `T: Sized` or `T: ?Sized` bound: - - - If callers have a `T: Sized` bound then there would be no breaking change - as `T: Sized` implies the changed or relaxed bound. - - If callers have a `T: ?Sized` bound then this is equivalent to a - `T: DynRuntimeSized` bound, as described earlier. Therefore there would - not be a breaking change as `T: DynRuntimeSized` implies the changed or - relaxed bound. - - ```rust - fn user_fn(value: T) { std_fn(value) } // T: Sized, so T: RuntimeSized - fn std_fn(value: T) { /* .. */ } - ``` - - If an existing function had a `?Sized` bound and this bound were changed to - `RuntimeSized` then this *would* be a breaking change, but it is not expected - that this change would be applied to any existing functions in the standard - library. - - The proposed traits in this RFC are only a non-breaking change because the - new auto traits being added are subtraits of `Sized`, adding supertraits of - `Sized` would be a breaking change. - -2. Trait objects passed by callers would not imply the new trait. For example, - adding a new auto trait as a bound to `std_fn` would cause `user_fn` to stop - compiling as its trait object would not automatically implement the new auto - trait: - - ```rust - fn user_fn(value: Box) { std_fn(value) } - fn std_fn(value: &T) { /* ... */} - //~^ ERROR the trait bound `dyn ExistingTrait: NewAutoTrait` is not satisfied in `Box` - ``` - - Like the previous case, due to the proposed traits being subtraits of - `Sized`, and every trait object implementing `Sized`, a new `RuntimeSized`, - `DynSized`, `DynRuntimeSized`, or `Pointee` bound would be already satisfied. - -3. Associated types of traits have default `Sized` bounds which may be being used. - For example, relaxing a `Sized` bound on `Add::Output` breaks a function - which takes a `T: Add` and passes `::Output` to `size_of` as not all - types which implement the relaxed bound will implement `Sized`. - - ```rust - trait Add { - type Output: RuntimeSized; - } - - fn user_fn() { - std::mem::size_of::<::Output>() - //~^ ERROR the trait bound `::Output: Sized` is not satisfied - } - ``` - - Relaxing the bounds of an associated type is in effect giving existing - parameters a less restrictive bound which may not be suitable. - - Unfortunately, this means it is not possible to change existing associated - type bounds to any of the proposed sizedness traits from this RFC. While it - may be desirable to relax the `Sized` bound on associated types, it shouldn't - be necessary to do so in this RFC and thus this should not be considered a - blocker. It may be possible to work around this over an edition, which is - discussed in the future possibilities section. - -## Risk of churn -[risk-of-churn]: #risk-of-churn +## Ecosystem churn +[ecosystem-churn]: #ecosystem-churn It is not expected that this RFC's additions would result in much churn within -the ecosystem. All bounds in the standard library should be re-evaluated -during the implementation of this RFC, but bounds in third-party crates need not -be. +the ecosystem. Almost all of the necessary changes would happen automatically +during edition migration. -`RuntimeSized` types will primarily be used for localised performance optimisation, +All bounds in the standard library should be re-evaluated during the +implementation of this RFC, but bounds in third-party crates need not be. + +As runtime-sized types will primarily be used for localised performance optimisation, and `Pointee` types will primarily be used for localised FFI, neither is expected to be so pervasive throughout Rust codebases to the extent that all existing -`Sized` or `?Sized` bounds would need to be immediately reconsidered in light -of their addition. If a user of a `RuntimeSized` type or a `Pointee` type did -encounter a bound that needed to be relaxed, this could be changed in a patch to -the relevant crate without breaking backwards compatibility as-and-when such -cases are encountered. +`const Sized`, `~const DynSized` or `DynSized` bounds (after edition migration) would +need to be immediately reconsidered in light of their addition, even if in many cases +these could be relaxed. + +If a user of a runtime-sized type or a `Pointee` type did encounter a bound that +needed to be relaxed, this could be changed in a patch to the relevant crate without +breaking backwards compatibility as-and-when such cases are encountered. -## Changes to the standard library -[changes-to-the-standard-library]: #changes-to-the-standard-library +If edition migration were able to attempt migrating each bound to a more relaxed bound +and then use the guaranteed-to-work bound as a last resort then this could further +minimise any changes required by users. + +## Other changes to the standard library +[other-changes-to-the-standard-library]: #other-changes-to-the-standard-library With these new traits and having established changes to existing bounds which can be made while preserving backwards compatibility, the following changes could be made to the standard library: -- [`std::mem::size_of`][api_size_of] and [`std::mem::align_of`][api_align_of] - - `T: Sized` becomes `T: RuntimeSized` - - As described previously, `RuntimeSized` is a superset of `Sized` so - this change does not break any existing callers. -- [`std::mem::size_of_val`][api_size_of_val] and - [`std::mem::align_of_val`][api_align_of_val] - - `T: ?Sized` becomes `T: DynRuntimeSized` - - As described previously, `?Sized` is equivalent to `DynRuntimeSized` due to the - existence of `size_of_val`, therefore this change does not break any existing - callers. - - `DynRuntimeSized` would not be implemented by `extern type`s from [rfcs#1861] - [rfc_extern_types], which would prevent these functions from being invoked on - types which have no known size or alignment. -- [`std::clone::Clone`][api_clone] - - `Clone: Sized` becomes `Clone: RuntimeSized` - - `RuntimeSized` is implemented by more types than `Sized` and by all types - which implement `Sized`, therefore any current implementor of `Clone` will not - break. - - `RuntimeSized` will be implemented by scalable vectors from - [rfcs#3268][rfc_scalable_vectors] whereas `Sized` would not have been, and - this is correct as `RuntimeSized` types can still be cloned. As `Copy: Clone`, - this allows scalable vectors to be `Copy`, which is necessary and currently a - blocker for that feature. - [`std::boxed::Box`][api_box] - - `T: ?Sized` becomes `T: DynRuntimeSized` + - `T: ?Sized` becomes `T: DynSized` - As before, this is not a breaking change and prevents types only - implementing `Pointee` from being used with `Box`, as these types do not have - the necessary size and alignment for allocation/deallocation. + implementing `Pointee` from being used with `Box`, as these types do + not have the necessary size and alignment for allocation/deallocation. -As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard -library would need to be reviewed and updated as appropriate. +As part of the implementation of this RFC, each `Sized`/`?Sized` bound in +the standard library would need to be reviewed and updated as appropriate. # Drawbacks [drawbacks]: #drawbacks - This is a fairly significant change to the `Sized` trait, which has been in the language since 1.0 and is now well-understood. -- This RFC's proposal that adding a bound of `RuntimeSized`, `DynSized`, - `DynRuntimeSized` or `Pointee` would remove the default `Sized` bound is - somewhat unintuitive. Typically adding a trait bound does not remove another - trait bound, however it's debatable whether this is more or less confusing - than existing `?Sized` bounds. +- This RFC's proposal that adding a bound of `const Sized`, `const DynSized`, + `DynSized`, `const Pointee` or `Pointee` would remove the default `Sized` + bound is somewhat unintuitive. Typically adding a trait bound does not + remove another trait bound, however it's debatable whether this is more or + less confusing than existing `?Sized` bounds. +- As this RFC depends on `const Trait`, it inherits all of the drawbacks of + `const Trait`. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives @@ -601,43 +623,29 @@ There are various points of difference to the [prior art](#prior-art) related to backwards compatibility hazards and that feels more appropriate for Rust. - In contrast to [rfcs#1524][rfc_custom_dst], items are not added to the `Sized` trait as this wouldn't be sufficient to capture the range in sizedness that this - RFC aims to capture (i.e. for scalable vectors with `RuntimeSized` or - `extern type`s with `Pointee`), even if it theoretically could enable Custom DSTs. + RFC aims to capture (i.e. for scalable vectors with `const Sized` or + extern types with `Pointee`), even if it theoretically could enable Custom DSTs. - In contrast to [rfcs#1993][rfc_opaque_data_structs], [rust#44469][pr_dynsized], [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], none of the traits proposed in this RFC are default bounds and therefore do not - need to support being relaxed bounds (i.e. no `?RuntimeSized`), which avoids + need to support being relaxed bounds (i.e. no `?DynSized`), which avoids additional language complexity and backwards compatibility hazards related to relaxed bounds and associated types. - In contrast to [rfcs#1524][rfc_custom_dst], [rfc#1993][rfc_opaque_data_structs], [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts], [Pre-RFC: Custom DSTs][prerfc_custom_dst] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], - `DynSized` does not have `size_of_val`/`align_of_val` methods to support custom DSTs, - but like [rfcs#2310][rfc_dynsized_without_dynsized], this RFC prefers keeping - these functions out of `DynSized` and having all of the traits be solely marker - traits. Custom DSTs are still compatible with this proposal using a `Contiguous` - trait as in [rfcs#2594][rfc_custom_dst_electric_boogaloo]. - -There are some alternatives to the decisions made in this RFC: - -- Instead of automatically upgrading `RuntimeSized` and `DynRuntimeSized` bounds - in `const` contexts, change it to these bounds permanently and add a new function - which cannot be `const` which accepts `RuntimeSized` and `DynRuntimeSized` types. - -## Bikeshedding -[bikeshedding]: #bikeshedding - -All of the trait names proposed in the RFC can be bikeshed and changed, they'll -ultimately need to be decided but aren't the important part of the RFC. + `DynSized` does not have `size_of_val`/`align_of_val` methods to support + custom DSTs as this would add to the complexity of this proposal and custom DSTs + are not this RFC's focus, see the [Custom DSTs][custom-dsts] section later. ## Why have `Pointee`? [why-have-pointee]: #why-have-pointee -It may seem that the `Pointee` trait is unnecessary as this is equivalent to the -absense of any bounds whatsoever, but having an `Pointee` trait is necessary to -enable the meaning of `?Sized` to be re-defined to be equivalent to `DynRuntimeSized` -and avoid complicated behaviour change over an edition. +It may seem that re-using the `Pointee` trait at the bottom of the trait hierarchy +is unnecessary as this is equivalent to the absense of any bounds whatsoever, but +having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined +to be equivalent to `const DynSized` and avoid complicated behaviour change over an edition. Without `Pointee`, if a user wanted to remove all sizedness bounds from a generic parameter then they would have two options: @@ -647,56 +655,106 @@ parameter then they would have two options: summarizes these discussions) 2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound -The latter is the only viable option, but this would complicate changing -`size_of_val`'s existing `?Sized` bound: - -Without `Pointee`, `?Sized` would be equivalent to `DynRuntimeSized` until -`extern type`s are stabilised (e.g. a `?Sized` bound would accept exactly the -same types as a `DynRuntimeSized` bound, but after `extern type`s are introduced, -`?Sized` bounds would accept `extern type`s and `DynRuntimeSized` bounds would not). -`extern type`s would need to be introduced over an edition and all existing `?Sized` -bounds rewritten to `?Sized + DynRuntimeSized`. This is the same mechanism described -in [rfcs#3396][rfc_extern_types_v2] to introduce it's `MetaSized` trait. - -## Why have a non-linear hierarchy? -[why-have-a-non-linear-hierarchy]: #why-have-a-non-linear-hierarchy - -Previous iterations of this RFC proposed `RuntimeSized: DynSized` and -`DynSized: Pointee` rather than having the diamond hierarchy that the RFC -currently has - but this was found to be unsuitable as `size_of_val` had -to have a `DynSized` bound, which meant it could also accept `RuntimeSized` -types, and this caused issues if `size_of_val` were to be made `const`. - -It may be possible to go back to a linear hierarchy of traits if the -RFC's current proposal upgrading of bounds in `const` contexts is kept. + This is the only viable option, but this would complicate changing + `size_of_val`'s existing `?Sized` bound: + + Without `Pointee`, `?Sized` would be equivalent to `const DynSized` until + extern types are stabilised (e.g. a `?Sized` bound would accept exactly the + same types as a `const DynSized` bound, but after extern types are introduced, + `?Sized` bounds would accept extern types and `const DynSized` bounds would not). + extern types would need to be introduced over an edition and all existing `?Sized` + bounds rewritten to `?Sized + const DynSized`. This is the same mechanism described + in [rfcs#3396][rfc_extern_types_v2] to introduce its `MetaSized` trait. + +## Why use const traits? +[why-use-const-traits]: #why-use-const-traits + +Previous iterations of this RFC had both linear[^5] and non-linear[^6] trait hierarchies +which included a `RuntimeSized` trait and did not use const traits. However, both of +these were found to be backwards-incompatible due to being unable to relax the +supertrait of `Clone`. Without const traits, it is not possible to represent +runtime-sized types. + +[^5]: In previous iterations, the proposed linear trait hierarchy was: + + ``` + ┌─────────────────────────────────────────────────┐ + │ ┌─────────────────────────────────────┐ │ + │ │ ┌────────────────────────┐ │ │ + │ │ │ ┌───────┐ │ │ │ + │ │ │ │ Sized │ RuntimeSized │ DynSized │ Pointee │ + │ │ │ └───────┘ │ │ │ + │ │ └────────────────────────┘ │ │ + │ └─────────────────────────────────────┘ │ + └─────────────────────────────────────────────────┘ + ``` + + This approach was scrapped once it became clear that a `const`-stable + `size_of_val` would need to be able to be instantiated with `DynSized` + types and not `RuntimeSized` types, and that this could not be + represented. +[^6]: In previous iterations, the proposed non-linear trait hierarchy was: + + ``` + ┌───────────────────────────────────────────────────────────────┐ + │ ┌───────────────────────────────────────────────────────────┐ │ + │ │ ┌──────────────────────────────┐┌───────────────────────┐ │ │ + │ │ │ ┌────────────────────────────┴┴─────────────────────┐ │ │ │ + │ │ │ │ Sized │ │ │ │ + │ │ │ │ {type, target} │ │ │ │ + │ │ │ └────────────────────────────┬┬─────────────────────┘ │ │ │ + │ │ │ RuntimeSized ││ DynSized │ │ │ + │ │ │ {type, target, runtime env} ││ {type, target, value} │ │ │ + │ │ └──────────────────────────────┘└───────────────────────┘ │ │ + │ │ DynRuntimeSized │ │ + │ │ {type, target, runtime env, value} │ │ + │ └───────────────────────────────────────────────────────────┘ │ + │ Pointee │ + │ {*} │ + └───────────────────────────────────────────────────────────────┘ + ``` + + This approach proposed modifying the bounds on the `size_of` function + from `RuntimeSized` to `Sized` when used in a const context (and from + `DynRuntimeSized` to `DynSized` for `size_of_val`) to try and work + around the issues with constness, but this would have been unsound, + and ultimately the inability to relax `Clone`'s supertrait made it + infeasible anyway. ## Alternatives to this accepting this RFC [alternatives-to-this-rfc]: #alternatives-to-this-rfc -There are not many alternatives to this RFC to unblock `extern type`s and +There are not many alternatives to this RFC to unblock extern types and scalable vectors: - Without this RFC, scalable vectors from [rfcs#3268][rfc_scalable_vectors] would remain blocked unless special-cased by the compiler in the type system. + - It is not possible to add these without const traits: relaxing the + supertrait of `Clone` is backwards-incompatible and const traits + are the only way to avoid that. - Extern types from [rfcs#1861][rfc_extern_types] would remain blocked if no action was taken, unless: - The language team decided that having `size_of_val` and `align_of_val` panic was acceptable. - The language team decided that having `size_of_val` and `align_of_val` return `0` and `1` respectively was acceptable. - - The language team decided that `extern type`s could not be instantiated + - The language team decided that extern types could not be instantiated into generics and that this was acceptable. - The language team decided that having `size_of_val` and `align_of_val` - produce post-monomorphisation errors for `extern type`s was acceptable. -- It would not be possible for this change to be implemented as a library or - macro instead of in the language itself. + produce post-monomorphisation errors for extern types was acceptable. + +## Bikeshedding +[bikeshedding]: #bikeshedding + +All of the trait names proposed in the RFC can be bikeshed and changed, they'll +ultimately need to be decided but aren't the important part of the RFC. # Prior art [prior-art]: #prior-art There have been many previous proposals and discussions attempting to resolve -the `size_of_val` and `align_of_val` for `extern type`s through modifications to +the `size_of_val` and `align_of_val` for extern types through modifications to the `Sized` trait, summarised below: - [rfcs#709: truly unsized types][rfc_truly_unsized_types], [mzabaluev][author_mzabaluev], Jan 2015 @@ -745,14 +803,14 @@ the `Sized` trait, summarised below: that prevents there use statically". Inventing an exact mechanism was intended to be completed by [rfcs#1524][rfc_custom_dst] or its like. - [rfcs#1993: Opaque Data structs for FFI][rfc_opaque_data_structs], [mystor][author_mystor], May 2017 - - This RFC was an alternative to the original `extern type`s RFC + - This RFC was an alternative to the original extern types RFC ([rfcs#1861][rfc_extern_types]) and introduced the idea of a `DynSized` auto trait. - Proposes a `DynSized` trait which was a built-in, unsafe, auto trait, a supertrait of `Sized`, and a default bound which could be relaxed with - `? DynSized`. + `?DynSized`. - It would automatically implemented for everything that didn't have an - `Opaque` type in it (this RFC's equivalent of an `extern type`). + `Opaque` type in it (RFC 1993's equivalent of an `extern type`). - `size_of_val` and `align_of_val` would have their bounds changed to `DynSized`. - Trait objects would have a `DynSized` bound by default and the @@ -767,7 +825,7 @@ the `Sized` trait, summarised below: - This was considering `DynSized` with a relaxed bound. - Anticipating some form of custom DSTs, there was the possibility that `size_of_val` could run user code and panic anyway, so making it panic - for `extern type`s wasn't as big an issue. `size_of_val` running in unsafe code + for extern types wasn't as big an issue. `size_of_val` running in unsafe code could be a footgun and that caused mild concern. - See [this comment](https://github.com/rust-lang/rust/issues/43467#issuecomment-377521693) and [this comment](https://github.com/rust-lang/rust/issues/43467#issuecomment-377665733). @@ -785,10 +843,10 @@ the `Sized` trait, summarised below: [rfcs#1993][rfc_opaque_data_structs] except it is implemented for every type with a known size and alignment at runtime, rather than requiring an `Opaque` type. - - In addition to preventing `extern type`s being used in `size_of_val` and + - In addition to preventing extern types being used in `size_of_val` and `align_of_val`, this PR is motivated by wanting to have a mechanism by which `!DynSized` types can be prevented from being valid in struct tails due to needing - to know the alignment of the tail in order to calculate its offset, and this is + to know the alignment of the tail in order to calculate its field offset. - `DynSized` had to be made a implicit supertrait of all traits in this implementation - it is presumed this is necessary to avoid unsized types implementing traits. @@ -807,7 +865,7 @@ the `Sized` trait, summarised below: bound](https://github.com/rust-lang/rust/pull/46108#issuecomment-353672604). - Ultimately this was closed [after a language team meeting](https://github.com/rust-lang/rust/pull/46108#issuecomment-360903211) deciding that `?DynSized` was ultimately too complex and couldn't be - justified by support for a relatively niche feature like `extern type`. + justified by support for a relatively niche feature like extern types. - [rfcs#2255: More implicit bounds (?Sized, ?DynSized, ?Move)][issue_more_implicit_bounds], [kennytm][author_kennytm], Dec 2017 - Issue created following [rust#46108][pr_dynsized_rebase] to discuss the complexities surrounding adding new traits which would benefit from relaxed @@ -833,7 +891,7 @@ the `Sized` trait, summarised below: - A `Thin` type to allow thin pointers to DSTs is also proposed in this pre-eRFC - it is a different `Thin` from the currently unstable `core::ptr::Thin` and it's out-of-scope for this RFC to include a similar type - and since accepted [rfcs#2580][rfc_pointer_metadata_vtable] overlaps. + and accepted [rfcs#2580][rfc_pointer_metadata_vtable] overlaps. - This pre-eRFC may be the origin of the idea for a family of `Sized` traits, later cited in [Sized, DynSized, and Unsized][blog_dynsized_unsized]. - [rfcs#2510][rfc_pointer_metadata_vtable] was later submitted which was a @@ -850,18 +908,18 @@ the `Sized` trait, summarised below: - The proposed `DynSized` trait in [rfcs#2310][rfc_dynsized_without_dynsized] is really quite similar to the trait proposed by this RFC except: - It includes an `#[assume_dyn_sized]` attribute to be added to - `T: ?Sized` bounds instead of replacing them with `T: DynRuntimeSized`, - which would warn instead of error when a non-`DynRuntimeSized` type is + `T: ?Sized` bounds instead of replacing them with `T: const DynSized`, + which would warn instead of error when a non-`constage DynSized` type is substituted into `T`. - This is to avoid a backwards compatibility break for uses of - `size_of_val` and `align_of_val` with `extern type`s, but it is - unclear why this is necessary given that `extern type`s are + `size_of_val` and `align_of_val` with extern types, but it is + unclear why this is necessary given that extern types are unstable. - - It does not include `RuntimeSized`, `DynRuntimeSized` or `Pointee`. + - It does not include `Pointee` or any of the const traits. - Adding an explicit bound for `DynSized` does not remove the implicit bound for `Sized`. - [rust#49708: `extern type` cannot support `size_of_val` and `align_of_val`][issue_extern_types_align_size], [joshtriplett][author_joshtriplett], Apr 2018 - - Primary issue for the `size_of_val`/`align_of_val` `extern type`s + - Primary issue for the `size_of_val`/`align_of_val` extern types blocker, following no resolution from either of [rfcs#1524][rfc_custom_dst] and [rust#44469][pr_dynsized] or their successors. - This issue largely just re-hashes the arguments made in other threads @@ -896,7 +954,7 @@ the `Sized` trait, summarised below: - Intrinsics are added for constructing a pointer to a dynamically sized type from its metadata and value, and for accessing the metadata of a dynamically sized type. - - `extern type`s do not implement `Contiguous` but do implement `Pointee`. + - extern types do not implement `Contiguous` but do implement `Pointee`. - `Contiguous` is a default bound and so has a relaxed form `?Contiguous`. - There's plenty of overlap here with [rfcs#2580][rfc_pointer_metadata_vtable] and its `Pointee` trait - the accepted [rfcs#2580][rfc_pointer_metadata_vtable] @@ -922,7 +980,7 @@ the `Sized` trait, summarised below: Neither new trait can be implemented by hand. - It's implied that `DynSized` is implemented for all dynamically sized types, but it isn't clear. - - Despite being relatively brief, this RFC has lots of comments. + - Despite being relatively brief, RFC 2984 has lots of comments. - The author argues that `?DynSized` is okay and disagrees with previous concerns about complexity and that all existing bounds would need to be reconsidered in light of `?DynSized`. @@ -939,7 +997,7 @@ the `Sized` trait, summarised below: - [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], [CAD97][author_cad97], May 2022 - This RFC proposes a forever-unstable default-bound unsafe trait `DynSized` with `size_of_val_raw` and `align_of_val_raw`, implemented for everything other - than `extern type`s. Users can implement `DynSized` for their own types. This + than extern types. Users can implement `DynSized` for their own types. This proposal doesn't say whether `DynSized` is a default bound but does mention a relaxed form of the trait `?DynSized`. - [rfcs#3319: Aligned][rfc_aligned], [Jules-Bertholet][author_jules_bertholet], Sep 2022 @@ -974,7 +1032,7 @@ the `Sized` trait, summarised below: - References to types that implement `DynSized` do not need to store the size in pointer metadata. Types implementing `DynSized` without other pointer metadata are thin pointers. - - This proposal has no solution for `extern type` limitations, its sole aim + - This proposal has no solution for extern type limitations, its sole aim is to enable more pointers to be thin pointers. - [Sized, DynSized, and Unsized][blog_dynsized_unsized], [Niko Matsakis][author_nikomatsakis], Apr 2024 - This proposes a hierarchy of `Sized`, `DynSized` and `Unsized` traits @@ -991,23 +1049,21 @@ below but not summarized: - [rfcs#9: RFC for "fat objects" for DSTs][rfc_fat_objects], [MicahChalmer][author_micahchalmer], Mar 2014 - [pre-RFC: unsized types][rfc_unsized_types], [japaric][author_japaric], Mar 2016 -There haven't been any particular proposals which have included an equivalent -of the `RuntimeSized` and `DynRuntimeSized` traits, as the scalable vector types -proposal in [RFC 3268][rfc_scalable_vectors] is relatively newer and less well known: +There haven't been any particular proposals which have included a solution for +runtime-sized types, as the scalable vector types proposal in [RFC 3268][rfc_scalable_vectors] +is relatively newer and less well known: - [rfcs#3268: Add scalable representation to allow support for scalable vectors][rfc_scalable_vectors], [JamieCunliffe][author_jamiecunliffe], May 2022 - Proposes temporarily special-casing scalable vector types to be able to implement `Copy` without implementing `Sized` and allows function return values to be `Copy` or `Sized` (not just `Sized`). - Neither of these changes would be necessary with this RFC, scalable - vectors would just be `RuntimeSized` types and function return values would - just need to implement `RuntimeSized`, not `Sized`. + vectors would just be `Sized` types (not `const Sized`) and function return + values would continue to need to implement `Sized`. To summarise the above exhaustive listing of prior art: -- No previous works have proposed an equivalent of a `RuntimeSized`, - `DynRuntimeSized` or `Pointee` trait ([Sized, DynSized, and - Unsized][blog_dynsized_unsized] is closest), only `DynSized`. +- No previous works have proposed an equivalent of the const size traits., - One proposal proposed adding a marker type that as a field would result in the containing type no longer implementing `Sized`. - Often proposals focused at Custom DSTs preferred to combine the @@ -1050,63 +1106,85 @@ this proposal: the presence of other size trait bounds. - [Sized, DynSized, and Unsized][blog_dynsized_unsized] is very similar and a major inspiration for this proposal. It has everything this proposal has except - for `RuntimeSized` and `DynRuntimeSized` and all the additional context an - RFC needs. + for the const size traits and all the additional context an RFC needs. + +Some prior art referenced [rust#21974][issue_regions_too_simplistic] as a limitation +of the type system which can result in new implicit bounds or implicit supertraits +being infeasible for implementation reasons, but [it is believed that this is no +longer relevant][zulip_issue_regions_too_simplistic]. # Unresolved questions [unresolved-questions]: #unresolved-questions -- [rust#21974][issue_regions_too_simplistic] has been linked in previous - `DynSized` proposals as a reason why `DynSized` (as then proposed) must be - an implicit supertrait of all traits, but it isn't obvious why this issue is - relevant. - - It seems like `DynSized` being a supertrait of all traits is necessary so - that `extern type`s do not implement any traits anyway. -- `extern type` `OpaqueListContents` is used as the last field of a struct in - rustc, which currently works because it has a presumed alignment of one. This - would be prohibited by the RFC as written, but relaxing this is listed as a - future possibility. - - Adding limitations to the use of `Pointee` types in structs could be left - as a follow-up as it is more related to `extern type` than extending `Sized` - anyway. Implementation of this RFC would not create any `Pointee` types. -- How would this interact with proposals to split alignment and sizedness into - separate traits, like [rfcs#3319][rfc_aligned]? -- Some prior art had different rules for automatically implementing `DynSized` - on structs/enums vs on unions - are those necessary for these traits? -- Could the existing `std::ptr::Pointee` trait be used as the supertrait - of all other sizedness traits? - - Otherwise `Pointee` as proposed would need renamed. -- How serious are the limitations on changing the bounds of associated types? +None currently. # Future possibilities [future-possibilities]: #future-possibilities - Additional size traits could be added as supertraits of `Sized` if there are - other delineations in sized-ness that make sense to be drawn. + other delineations in sized-ness that make sense to be drawn (subject to + avoiding backwards-incompatibilities when changing APIs). - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2] could be added between `Sized` and `DynSized` - The requirement that users cannot implement any of these traits could be - relaxed in future to support custom DSTs or any other proposed feature which - required it. + relaxed in future if required. - Depending on a trait which has one of the proposed traits as a supertrait could imply a bound of the proposed trait, enabling the removal of boilerplate. + - However, this would limit the ability to relax a supertrait, e.g. if + `trait Clone: Sized` and `T: Clone` is used as a bound of a function + and `Sized` is relied on in that function, then the supertrait of + `Clone` could no longer be relaxed as it can today. - Consider allowing associated type bounds to be relaxed over an edition. - i.e. `type Output: if_rust_2021(Sized) + NewAutoTrait` or something like that, out of scope for this RFC. -- This proposal is compatible with adding Custom DSTs in future. - - Leveraging the already accepted [rfcs#2580][rfc_pointer_metadata_vtable] - and taking an approach similar to [rfcs#2594][rfc_custom_dst_electric_boogaloo] - with its `Contiguous` trait to provide somewhere for `size_of_val` and - `align_of_val` to be implemented for the Custom DST. - - Custom DSTs would need to implement `DynSized` and not implement - `Sized` to be compatible with the traits introduced in this proposal, and maybe - you'd want to add `Pointee` as a supertrait of `DynSized` with some non-`()` - metadata type, or something along those lines. + +## Alignment +[alignment]: #alignment + +There has been community interest in an [`Aligned` trait][rfc_aligned] and there +are examples of `Aligned` traits being added in the ecosystem: + +- `rustc` has [its own `Aligned` trait][rustc_aligned] to support pointer tagging. +- [`unsized-vec`][crate_unsized_vec] implements a `Vec` that depends on knowing + whether a type has an alignment or not. + +Furthermore, the existing incorrect behaviour of `align_of_val` returning one is +used in `rustc` with the [`OpaqueListContents` type][rustc_opaquelistcontents] to +implement a custom DST. This use case would break with this RFC as written, as +the alignment of an extern type would correctly be undefined. + +An `Aligned` trait could be added to this proposal between `DynSized` and `Pointee` +in the trait hierarchy which would be implemented automatically by the compiler for +all `DynSized` and `Sized` types, but could be implemented for extern types by +users when the alignment of an extern type is known. Any type implementing `Aligned` +could be used as the last element in a compound type. + +## Custom DSTs +[custom-dsts]: #custom-dsts + +Given the community interest in supporting custom DSTs in future (see +[prior art][prior-art]), this RFC was written considering future-compatibility with +custom DSTs in mind. + +There are various future changes to these traits which could be used to support +custom DSTs on top of this RFC. None of these have been considered thoroughly, and are +written here only to illustrate. + +- Allow `Pointee` to be implemented manually on user types, which would replace + the compiler's implementation. +- Introduce a trait like [rfcs#2594][rfc_custom_dst_electric_boogaloo]'s `Contiguous` + which users can implement on their custom DSTs, or add methods to `DynSized` and + allow it to be implemented by users. +- Introduce intrinsics which enable creation of pointers with metadata and for + accessing the metadata of a pointer. [api_align_of]: https://doc.rust-lang.org/std/mem/fn.align_of.html [api_align_of_val]: https://doc.rust-lang.org/std/mem/fn.align_of_val.html [api_box]: https://doc.rust-lang.org/std/boxed/struct.Box.html +[api_copy]: https://doc.rust-lang.org/std/marker/trait.Copy.html [api_clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html +[api_pointee]: https://doc.rust-lang.org/std/ptr/trait.Pointee.html +[api_sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html [api_size_of]: https://doc.rust-lang.org/std/mem/fn.size_of.html [api_size_of_val]: https://doc.rust-lang.org/std/mem/fn.size_of_val.html [author_aturon]: https://github.com/aturon @@ -1132,9 +1210,11 @@ this proposal: [author_ubsan]: https://github.com/ubsan [blog_dynsized_unsized]: https://smallcultfollowing.com/babysteps/blog/2024/04/23/dynsized-unsized/ [changing_rules_of_rust]: https://without.boats/blog/changing-the-rules-of-rust/ +[crate_unsized_vec]: https://docs.rs/unsized-vec/0.0.2-alpha.7/unsized_vec/ [design_meeting]: https://hackmd.io/7r3_is6uTz-163fsOV8Vfg [design_notes_dynsized_constraints]: https://github.com/rust-lang/lang-team/blob/master/src/design_notes/dynsized_constraints.md [erfc_minimal_custom_dsts_via_extern_type]: https://internals.rust-lang.org/t/erfc-minimal-custom-dsts-via-extern-type-dynsized/16591?u=cad97 +[goal_const_traits]: https://rust-lang.github.io/rust-project-goals/2024h2/const-traits.html [issue_extern_types_align_size]: https://github.com/rust-lang/rust/issues/49708 [issue_more_implicit_bounds]: https://github.com/rust-lang/rfcs/issues/2255 [issue_regions_too_simplistic]: https://github.com/rust-lang/rust/issues/21974#issuecomment-331886186 @@ -1160,3 +1240,6 @@ this proposal: [rfc_truly_unsized_types]: https://github.com/rust-lang/rfcs/pull/709 [rfc_unsized_types]: https://github.com/japaric/rfcs/blob/unsized2/text/0000-unsized-types.md [rfc_virtual_structs]: https://github.com/rust-lang/rfcs/pull/5 +[rustc_aligned]: https://github.com/rust-lang/rust/blob/a76ec181fba25f9fe64999ec2ae84bdc393560f2/compiler/rustc_data_structures/src/aligned.rs#L22-L25 +[rustc_opaquelistcontents]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/list/foreigntype.OpaqueListContents.html +[zulip_issue_regions_too_simplistic]: https://rust-lang.zulipchat.com/#narrow/channel/144729-t-types/topic/.2321984.20.2B.20implicit.20supertraits.20-.20still.20relevant.3F/near/477630998 From 9eecd57f92b47911f0645c03d2b1fb67b526cb72 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 22 Oct 2024 09:23:24 +0100 Subject: [PATCH 07/45] improved diagram --- text/0000-sized-hierarchy.md | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 4e6467f304b..6d7b7958795 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -175,24 +175,26 @@ also implements `DynSized`. Likewise, `DynSized` is a supertrait of `Pointee`. `const` if-and-only-if `Pointee` is `const`. ``` -┌──────────────────────────────────────────────────────────────────┐ -│ ┌──────────────────────────────────────────────────────────────┐ │ -│ │ ┌──────────────────────────────────────────────────────────┐ │ │ -│ │ │ ┌────────────────┐ │ │ │ -│ │ │ │ const Sized │ Sized │ │ │ -│ │ │ │ {type, target} │ {type, target, runtime env} │ │ │ -│ │ │ └────────────────┘ │ │ │ -│ │ └──────────────────────────────────────────────────────────┘ │ │ -│ │ ┌───────────────────────┐ │ │ -│ │ │ const DynSized │ DynSized │ │ -│ │ │ {type, target, value} │ {type, target, runtime env, value} │ │ -│ │ └───────────────────────┘ │ │ -│ └──────────────────────────────────────────────────────────────┘ │ -│ ┌───────────────────────┐ │ -│ │ const Pointee │ Pointee │ -│ │ {*} │ {*, runtime env} │ -│ └───────────────────────┘ │ -└──────────────────────────────────────────────────────────────────┘ +┌──────────────────────────────────────────────────────────────────────────┐ +│ ┌──────────────────────────────────────────────────────────────────────┐ │ +│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ │ +│ │ ┃ ┌─────────────────────────────╂──────────────────────────────────┐ │ │ +│ │ ┃ │ ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ │ │ │ +│ │ ┃ │ ┃ ┏━━━━━━━━━━━━━━━━━━━┓ ┃ ┃ │ │ │ +│ │ ┃ │ ┃ ┃ const Sized ┃ ┃ ┃ Sized │ │ │ +│ │ ┃ │ ┃ ┃ {type, target} ┃ ┃ ┃ {type, target, runtime env} │ │ │ +│ │ ┃ │ ┃ ┗━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ +│ │ ┃ └─╂───────────────────────╂───╂──────────────────────────────────┘ │ │ +│ │ ┃ ┃ ┃ ┃ │ │ +│ │ ┃ ┃ const DynSized ┃ ┃ DynSized │ │ +│ │ ┃ ┃ {type, target, value} ┃ ┃ {type, target, runtime env, value} │ │ +│ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ +│ └─╂───────────────────────────────╂────────────────────────────────────┘ │ +│ ┃ ┃ │ +│ ┃ const Pointee ┃ Pointee │ +│ ┃ {*} ┃ {*, runtime env} │ +│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ +└──────────────────────────────────────────────────────────────────────────┘ ``` `const Sized` is implemented on types which require knowledge of only the From 55b877983da7d94f54212cb55eb8b8224fe9fd85 Mon Sep 17 00:00:00 2001 From: David Wood Date: Wed, 30 Oct 2024 10:16:10 +0000 Subject: [PATCH 08/45] address review comments --- text/0000-sized-hierarchy.md | 51 ++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 6d7b7958795..873523f73ec 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -23,10 +23,10 @@ feature. [background]: #background Rust has the [`Sized`][api_sized] marker trait which indicates that a type's size -is statically known at compilation time. `Sized` is an trait which is -automatically implemented by the compiler on any type that has a statically known -size. All type parameters have a default bound of `Sized` and `?Sized` syntax can -be used to remove this bound. +is statically known at compilation time. `Sized` is a trait which is automatically +implemented by the compiler on any type that has a statically known size. All type +parameters have a default bound of `Sized` and `?Sized` syntax can be used to remove +this bound. There are two functions in the standard library which can be used to get a size, [`std::mem::size_of`][api_size_of] and [`std::mem::size_of_val`][api_size_of_val]: @@ -155,11 +155,12 @@ example: - [`std::mem::size_of_val`][api_size_of_val] computes the size of a value, and thus cannot accept extern types which have no size, and this should be prevented by the type system. -- Rust allows dynamically-sized types to be used as struct fields, but the - alignment of the type must be known, which is not the case for extern types. +- Rust allows dynamically-sized types to be used as the final field in a struct, + but the alignment of the type must be known, which is not the case for extern + types. - Allocation and deallocation of an object with `Box` requires knowledge of its size and alignment, which extern types do not have. -- For a value type to be allocated on the stack, it needs to have statically +- For a value type to be allocated on the stack, it needs to have constant known size, which dynamically-sized and unsized types do not have (but sized and "runtime sized" types do). @@ -248,20 +249,26 @@ Introduce a new marker trait, `DynSized`, adding it to a trait hierarchy with th traits `const`: ``` - ┌────────────────┐ - │ const Sized │ - │ {type, target} │ - └────────────────┘ - │ -┌───────────┴───────────┐ -│ const DynSized │ -│ {type, target, value} │ -└───────────────────────┘ - │ - ┌───────┴───────┐ - │ const Pointee │ - │ {*} │ - └───────────────┘ + ┌────────────────┐ ┌─────────────────────────────┐ + │ const Sized │ ───────────────→ │ Sized │ + │ {type, target} │ implies │ {type, target, runtime env} │ + └────────────────┘ └─────────────────────────────┘ + │ │ + implies implies + │ │ + ↓ ↓ +┌───────────────────────┐ ┌────────────────────────────────────┐ +│ const DynSized │ ──────────→ │ DynSized │ +│ {type, target, value} │ implies │ {type, target, runtime env, value} │ +└───────────────────────┘ └────────────────────────────────────┘ + │ │ + implies implies + │ │ + ↓ ↓ + ┌───────────────┐ ┌──────────────────┐ + │ const Pointee │ ──────────────────────→ │ Pointee │ + │ {*} │ implies │ {runtime env, *} │ + └───────────────┘ └──────────────────┘ ``` Or, in Rust syntax: @@ -375,7 +382,7 @@ a trait's associated type[^4] for the proposed traits. fn size_of_val(x: val) { /* .. */ } // `Foo` bound is new! ``` - ...then user could would break: + ...then user code would break: ```rust fn do_stuff(value: Box) { size_of_val(value) } From 7849df047f1635de3c281bda31bd65f74adfe7e0 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 12 Nov 2024 13:36:23 +0000 Subject: [PATCH 09/45] future possibility for externref --- text/0000-sized-hierarchy.md | 73 +++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 873523f73ec..04a32edbbf9 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -79,7 +79,6 @@ Throughout the RFC, the following terminology will be used: - The bounds on the generic parameters of a function may be referred to simply as the bounds on the function (e.g. "the caller's bounds"). - # Motivation [motivation]: #motivation @@ -1147,6 +1146,78 @@ None currently. - i.e. `type Output: if_rust_2021(Sized) + NewAutoTrait` or something like that, out of scope for this RFC. +## externref +[externref]: #externref + +Another compelling feature that requires extensions to Rust's sizedness traits to +fully support is wasm's `externref`. `externref` types are opaque types that cannot +be put in memory [^7]. `externref`s are used as abstract handles to resources in the +host environment of the wasm program, such as a JavaScript object. Similarly, when +targetting some GPU IRs (such as SPIR-V), there are types which are opaque handles +to resources (such as textures) and these types, like wasm's `externref`, cannot +be put in memory. + +[^7]: When Rust is compiled to wasm, we can think of the memory of the Rust program +as being backed by something like a `[u8]`, `externref`s exist outside of that `[u8]` +and there is no way to put an `externref` into this memory, so it is impossible to have +a reference or pointer to a `externref`. `wasm-bindgen` currently supports `externref` +by creating a array of the items which would be referenced by an `externref` on the +host side and passes indices into this array across the wasm-host boundary in lieu +of `externref`s. It isn't possible to support opaque types from some GPU targets using +this technique. + +`externref` are similar to `Pointee` in that the type's size is not known, but unlike +`Pointee` cannot be used behind a pointer. This RFC's proposed hierarchy of traits could +support this by adding another supertrait, `Value`: + +``` + ┌────────────────┐ ┌─────────────────────────────┐ + │ const Sized │ ───────────────→ │ Sized │ + │ {type, target} │ implies │ {type, target, runtime env} │ + └────────────────┘ └─────────────────────────────┘ + │ │ + implies implies + │ │ + ↓ ↓ +┌───────────────────────┐ ┌────────────────────────────────────┐ +│ const DynSized │ ──────────→ │ DynSized │ +│ {type, target, value} │ implies │ {type, target, runtime env, value} │ +└───────────────────────┘ └────────────────────────────────────┘ + │ │ + implies implies + │ │ + ↓ ↓ + ┌───────────────┐ ┌──────────────────┐ + │ const Pointee │ ──────────────────────→ │ Pointee │ + │ {*} │ implies │ {runtime env, *} │ + └───────────────┘ └──────────────────┘ + │ │ + implies implies + │ │ + ↓ ↓ + ┌─────────────┐ ┌──────────────────┐ + │ const Value │ ───────────────────────→ │ Value │ + │ {*} │ implies │ {runtime env, *} │ + └─────────────┘ └──────────────────┘ +``` + +`Pointee` is still defined as being implemented for any type that can be used +behind a pointer and may not be sized at all, this would be implemented for +effectively every type except wasm's `externref` (or similar opaque types from +some GPU targets). `Value` is defined as being implemented for any type that can +be used as a value, which is all types, and also may not be sized at all. + +Earlier in this RFC, `extern type`s have previously been described as not being +able to be used as a value, but it could instead be permitted to write functions +which use extern types as values (e.g. such as taking an extern type as an argument), +and instead rely on it being impossible to get a extern type that is not behind a +pointer or a reference. This also implies that `DynSized` types can be used as values, +which would remain prohibited behind the `unsized_locals` and `unsized_fn_params` +features until these are stabilised. + +With these changes to the RFC, it would be possible to support wasm's `externref` and +opaque types from some GPU targets. + ## Alignment [alignment]: #alignment From f5a5cfcd6237e0ca9241e7cddfbf4788f29fbf8f Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 10:39:13 +0000 Subject: [PATCH 10/45] dynsized -> valuesized --- text/0000-sized-hierarchy.md | 198 ++++++++++++++++++----------------- 1 file changed, 100 insertions(+), 98 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 04a32edbbf9..fb083f9d70f 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -69,9 +69,9 @@ implement `Sized` in the same sense as "unsized" is colloquially used. Throughout the RFC, the following terminology will be used: - "`Trait` types" will be used to refer to those types which implement `Trait` - and all of its supertraits but none of its subtraits. For example, a `DynSized` - type would be a type which implements `DynSized`, and `Pointee`, but not - `Sized`. `[usize]` would be referred to as a "`DynSized` type". + and all of its supertraits but none of its subtraits. For example, a `ValueSized` + type would be a type which implements `ValueSized`, and `Pointee`, but not + `Sized`. `[usize]` would be referred to as a "`ValueSized` type". - "Runtime-sized" types will be used those types whose size is statically known but only at runtime. These would include the scalable vector types mentioned in the motivation below, or those that implement `Sized` but not `const Sized` @@ -165,13 +165,13 @@ example: Rust uses marker traits to indicate the necessary knowledge required to know the size of a type, if it can be known. There are three traits related to the size -of a type in Rust: `Sized`, `DynSized`, and the existing unstable +of a type in Rust: `Sized`, `ValueSized`, and the existing unstable `std::ptr::Pointee`. Each of these traits can be implemented as `const` when the size is knowable at compilation time. -`Sized` is a supertrait of `DynSized`, so every type which implements `Sized` -also implements `DynSized`. Likewise, `DynSized` is a supertrait of `Pointee`. -`Sized` is `const` if-and-only-if `DynSized` is `const`, and `DynSized` is +`Sized` is a supertrait of `ValueSized`, so every type which implements `Sized` +also implements `ValueSized`. Likewise, `ValueSized` is a supertrait of `Pointee`. +`Sized` is `const` if-and-only-if `ValueSized` is `const`, and `ValueSized` is `const` if-and-only-if `Pointee` is `const`. ``` @@ -186,7 +186,7 @@ also implements `DynSized`. Likewise, `DynSized` is a supertrait of `Pointee`. │ │ ┃ │ ┃ ┗━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ │ │ ┃ └─╂───────────────────────╂───╂──────────────────────────────────┘ │ │ │ │ ┃ ┃ ┃ ┃ │ │ -│ │ ┃ ┃ const DynSized ┃ ┃ DynSized │ │ +│ │ ┃ ┃ const ValueSized ┃ ┃ ValueSized │ │ │ │ ┃ ┃ {type, target, value} ┃ ┃ {type, target, runtime env, value} │ │ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ └─╂───────────────────────────────╂────────────────────────────────────┘ │ @@ -210,13 +210,13 @@ not `const`). For example, `svint8_t` is a scalable vector type (from implementation of the target (i.e. it may be 128 bits on one processor and 256 bits on another). -`const DynSized` requires more knowledge than `const Sized` to compute the size: +`const ValueSized` requires more knowledge than `const Sized` to compute the size: it may additionally require a value (therefore `size_of` is not implemented for -`DynSized`, only `size_of_val`). For example, `[usize]` implements -`const DynSized` as knowing the type and target is not sufficient, the number of +`ValueSized`, only `size_of_val`). For example, `[usize]` implements +`const ValueSized` as knowing the type and target is not sufficient, the number of elements in the slice must also be known, which requires having the value. -As `Sized` is to `const Sized`, `DynSized` is to `const DynSized`: `DynSized` +As `Sized` is to `const Sized`, `ValueSized` is to `const ValueSized`: `ValueSized` requires a value, knowledge of the type and target platform, and can only be computed at runtime. For example, `[svint8_t]` requires a value to know how many elements there are, and then information from the runtime environment @@ -232,18 +232,18 @@ environment is not required (e.g. `const Pointee` for `u32` and `[usize]` but bare `Pointee` for `svint8_t` or `[svint8_t]`). All type parameters have an implicit bound of `const Sized` which will be -automatically removed if a `Sized`, `const DynSized`, `DynSized`, `const Pointee` -or `Pointee` bound is present instead. +automatically removed if a `Sized`, `const ValueSized`, `ValueSized`, +`const Pointee` or `Pointee` bound is present instead. -Prior to the introduction of `DynSized` and `Pointee`, `Sized`'s implicit bound +Prior to the introduction of `ValueSized` and `Pointee`, `Sized`'s implicit bound (now a `const Sized` implicit bound) could be removed using the `?Sized` syntax, -which is now equivalent to a `DynSized` bound in non-`const fn`s and -`~const DynSized` in `const fn`s and will be deprecated in the next edition. +which is now equivalent to a `ValueSized` bound in non-`const fn`s and +`~const ValueSized` in `const fn`s and will be deprecated in the next edition. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Introduce a new marker trait, `DynSized`, adding it to a trait hierarchy with the +Introduce a new marker trait, `ValueSized`, adding it to a trait hierarchy with the [`Sized`][api_sized] and [`Pointee`][api_pointee] traits, and make all sizedness traits `const`: @@ -257,7 +257,7 @@ traits `const`: │ │ ↓ ↓ ┌───────────────────────┐ ┌────────────────────────────────────┐ -│ const DynSized │ ──────────→ │ DynSized │ +│ const ValueSized │ ──────────→ │ ValueSized │ │ {type, target, value} │ implies │ {type, target, runtime env, value} │ └───────────────────────┘ └────────────────────────────────────┘ │ │ @@ -273,9 +273,9 @@ traits `const`: Or, in Rust syntax: ```rust= -const trait Sized: ~const DynSized {} +const trait Sized: ~const ValueSized {} -const trait DynSized: ~const std::ptr::Pointee {} +const trait ValueSized: ~const std::ptr::Pointee {} ``` `Pointee` was specified in [rfcs#2580][rfc_pointer_metadata_vtable] and is @@ -292,35 +292,35 @@ the compiler and cannot be implemented manually: - Types that which can be used from behind a pointer (they may or may not have a size). - `Pointee` will be implemented for: - - `DynSized` types + - `ValueSized` types - compound types where every element is `Pointee` - `const Pointee` will be implemented for: - - `const DynSized` types + - `const ValueSized` types - `extern type`s from [rfcs#1861][rfc_extern_types] - compound types where every element is `const Pointee` - In practice, every type will implement `Pointee` (as in [rfcs#2580][rfc_pointer_metadata_vtable]). -- `DynSized` +- `ValueSized` - Types whose size is computable given a value, and knowledge of the type, target platform and runtime environment. - - `const DynSized` does not require knowledge of the runtime + - `const ValueSized` does not require knowledge of the runtime environment - - `DynSized` is a subtrait of `Pointee` - - `DynSized` will be implemented for: + - `ValueSized` is a subtrait of `Pointee` + - `ValueSized` will be implemented for: - `Sized` types - - slices `[T]` where every element is `DynSized` - - compound types where every element is `DynSized` - - `const DynSized` will be implemented for: + - slices `[T]` where every element is `ValueSized` + - compound types where every element is `ValueSized` + - `const ValueSized` will be implemented for: - `const Sized` types - slices `[T]` where every element is `const Sized` - string slice `str` - trait objects `dyn Trait` - - compound types where every element is `const DynSized` + - compound types where every element is `const ValueSized` - `Sized` - Types whose size is computable given knowledge of the type, target platform and runtime environment. - `const Sized` does not require knowledge of the runtime environment - - `Sized` is a subtrait of `DynSized`. + - `Sized` is a subtrait of `ValueSized`. - `Sized` will be implemented for: - scalable vectors from [rfcs#3268][rfc_scalable_vectors] - compound types where every element is `Sized` @@ -339,9 +339,9 @@ Introducing new automatically implemented traits is backwards-incompatible, at least if you try to add it as a bound to an existing function[^1][^2] (and new auto traits that which go unused aren't that useful), but due to being supertraits of `Sized` and `Sized` being a default bound, these -backwards-incompatibilities are avoided for `DynSized` and `Pointee`. +backwards-incompatibilities are avoided for `ValueSized` and `Pointee`. -Relaxing a bound from `Sized` to `DynSized` or `Pointee` is non-breaking as +Relaxing a bound from `Sized` to `ValueSized` or `Pointee` is non-breaking as the calling bound must have either `T: Sized` or `T: ?Sized`, both of which would satisfy any relaxed bound[^3]. @@ -392,22 +392,22 @@ a trait's associated type[^4] for the proposed traits. | Before ed. migration | After ed. migration | | --------------------------------- | ------------------- | | `T: Sized` (implicit or explicit) | `T: const Sized` | - | `T: ?Sized` | `T: const DynSized` | + | `T: ?Sized` | `T: const ValueSized` | Any existing function in the standard library with a `T: Sized` bound could be changed to one of the following bounds and remain compatible with any callers that currently exist (as per the above table): - | | `const Sized` | `Sized` | `const DynSized` | `DynSized` | `const Pointee` | `Pointee` - | -------------- | ------------- | ------- | ---------------- | ---------- | --------------- | --------- - | `const Sized` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ + | | `const Sized` | `Sized` | `const ValueSized` | `ValueSized` | `const Pointee` | `Pointee` + | -------------- | ------------- | ------- | ------------------ | ------------ | --------------- | --------- + | `const Sized` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ Likewise with a `T: ?Sized` bound: - | | `const DynSized` | `DynSized` | `const Pointee` | `Pointee` - | ---------------- | ---------------- | ---------- | --------------- | --------- - | `const Sized` | ✔ | ✔ | ✔ | ✔ - | `const DynSized` | ✔ | ✔ | ✔ | ✔ + | | `const ValueSized` | `ValueSized` | `const Pointee` | `Pointee` + | ------------------ | ------------------ | ------------ | --------------- | --------- + | `const Sized` | ✔ | ✔ | ✔ | ✔ + | `const ValueSized` | ✔ | ✔ | ✔ | ✔ [^4]: Associated types of traits have default `Sized` bounds which cannot be relaxed. For example, relaxing a `Sized` bound on `Add::Output` breaks a function which takes a `T: Add` and passes `::Output` to @@ -419,7 +419,7 @@ a trait's associated type[^4] for the proposed traits. ```rust= trait Add { - type Output: DynSized; + type Output: ValueSized; } ``` @@ -436,23 +436,23 @@ a trait's associated type[^4] for the proposed traits. ## `Sized` bounds [sized-bounds]: #sized-bounds -`?Sized` would be made syntactic sugar for a `const DynSized` bound. A -`const DynSized` bound is equivalent to a `?Sized` bound as all values in Rust +`?Sized` would be made syntactic sugar for a `const ValueSized` bound. A +`const ValueSized` bound is equivalent to a `?Sized` bound as all values in Rust today whose types do not implement `Sized` are valid arguments to [`std::mem::size_of_val`][api_size_of_val] and as such have a size which can be computed given a value and knowledge of the type and target platform, and -therefore will implement `const DynSized`. As there are currently no -extern types or other types which would not implement `const DynSized`, +therefore will implement `const ValueSized`. As there are currently no +extern types or other types which would not implement `const ValueSized`, every type in Rust today which would satisfy a `?Sized` bound would satisfy -a `const DynSized` bound. +a `const ValueSized` bound. **Edition change:** In the current edition,`?Sized` will be syntatic sugar for -a `const DynSized` bound. In the next edition, use of `?Sized` syntax will be prohibited -over an edition and all uses of it will be rewritten to a `const DynSized` bound. +a `const ValueSized` bound. In the next edition, use of `?Sized` syntax will be prohibited +over an edition and all uses of it will be rewritten to a `const ValueSized` bound. A default implicit bound of `const Sized` is added by the compiler to every type -parameter `T` that does not have an explicit `Sized`, `?Sized`, `const DynSized`, -`DynSized`, `const Pointee` or `Pointee` bound. It is backwards compatible to change +parameter `T` that does not have an explicit `Sized`, `?Sized`, `const ValueSized`, +`ValueSized`, `const Pointee` or `Pointee` bound. It is backwards compatible to change the current implicit `Sized` bound to an `const Sized` bound as every type which exists currently will implement `const Sized`. @@ -460,11 +460,11 @@ exists currently will implement `const Sized`. sugar for `const Sized`. In the next edition, existing `Sized` bounds will be rewritten to `const Sized` bounds and new bare `Sized` bounds will be non-const. -An implicit `const DynSized` bound is added to the `Self` type of traits. Like +An implicit `const ValueSized` bound is added to the `Self` type of traits. Like implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, `Sized`, -`DynSized` or `Pointee` bound is present. +`ValueSized` or `Pointee` bound is present. -As`DynSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` +As `ValueSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` for these traits. ## `size_of` and `size_of_val` @@ -499,19 +499,19 @@ fn uses_size_of() -> usize { ``` [`size_of_val`][api_size_of_val] is currently const-unstable, so its bound can be -changed from `?Sized` to `~const DynSized` without any backwards compatibility +changed from `?Sized` to `~const ValueSized` without any backwards compatibility issues. ```rust= pub const fn size_of_val(val: &T) -> usize where - T: ~const DynSized, + T: ~const ValueSized, { /* .. */ } ``` -While `DynSized` is equivalent to the current `?Sized` bound it replaces, it +While `ValueSized` is equivalent to the current `?Sized` bound it replaces, it excludes extern types (which `?Sized` by definition cannot), which prevents `size_of_val` from being called with extern types from [rfcs#1861][rfc_extern_types]. @@ -535,7 +535,7 @@ implement `Clone` and `Copy`. `Pointee` types cannot be used in non-`#[repr(transparent)]` compound types as the alignment of these types would need to be known in order to calculate field offsets. `const Sized` types can be used in compound types with no restrictions. -`Sized`, `const DynSized` and `DynSized` types can be used in compound types, but +`Sized`, `const ValueSized` and `ValueSized` types can be used in compound types, but only as the last element. ## Compiler performance implications @@ -557,12 +557,12 @@ automatically imply the proposed trait in any bounds where the trait is used, e.g. ```rust -trait NewTrait: DynSized {} +trait NewTrait: ValueSized {} struct NewRc {} // equiv to `T: NewTrait + Sized` as today ``` -If the user wanted `T: DynSized` then it would need to be written explicitly. +If the user wanted `T: ValueSized` then it would need to be written explicitly. This is forward compatible with trait bounds which have sizedness supertraits implying the removal of the default `const Sized` bound. @@ -579,7 +579,7 @@ implementation of this RFC, but bounds in third-party crates need not be. As runtime-sized types will primarily be used for localised performance optimisation, and `Pointee` types will primarily be used for localised FFI, neither is expected to be so pervasive throughout Rust codebases to the extent that all existing -`const Sized`, `~const DynSized` or `DynSized` bounds (after edition migration) would +`const Sized`, `~const ValueSized` or `ValueSized` bounds (after edition migration) would need to be immediately reconsidered in light of their addition, even if in many cases these could be relaxed. @@ -599,7 +599,7 @@ can be made while preserving backwards compatibility, the following changes could be made to the standard library: - [`std::boxed::Box`][api_box] - - `T: ?Sized` becomes `T: DynSized` + - `T: ?Sized` becomes `T: ValueSized` - As before, this is not a breaking change and prevents types only implementing `Pointee` from being used with `Box`, as these types do not have the necessary size and alignment for allocation/deallocation. @@ -612,8 +612,8 @@ the standard library would need to be reviewed and updated as appropriate. - This is a fairly significant change to the `Sized` trait, which has been in the language since 1.0 and is now well-understood. -- This RFC's proposal that adding a bound of `const Sized`, `const DynSized`, - `DynSized`, `const Pointee` or `Pointee` would remove the default `Sized` +- This RFC's proposal that adding a bound of `const Sized`, `const ValueSized`, + `ValueSized`, `const Pointee` or `Pointee` would remove the default `Sized` bound is somewhat unintuitive. Typically adding a trait bound does not remove another trait bound, however it's debatable whether this is more or less confusing than existing `?Sized` bounds. @@ -637,13 +637,13 @@ There are various points of difference to the [prior art](#prior-art) related to [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], none of the traits proposed in this RFC are default bounds and therefore do not - need to support being relaxed bounds (i.e. no `?DynSized`), which avoids + need to support being relaxed bounds (i.e. no `?ValueSized`), which avoids additional language complexity and backwards compatibility hazards related to relaxed bounds and associated types. - In contrast to [rfcs#1524][rfc_custom_dst], [rfc#1993][rfc_opaque_data_structs], [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts], [Pre-RFC: Custom DSTs][prerfc_custom_dst] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], - `DynSized` does not have `size_of_val`/`align_of_val` methods to support + `ValueSized` does not have `size_of_val`/`align_of_val` methods to support custom DSTs as this would add to the complexity of this proposal and custom DSTs are not this RFC's focus, see the [Custom DSTs][custom-dsts] section later. @@ -653,12 +653,12 @@ There are various points of difference to the [prior art](#prior-art) related to It may seem that re-using the `Pointee` trait at the bottom of the trait hierarchy is unnecessary as this is equivalent to the absense of any bounds whatsoever, but having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined -to be equivalent to `const DynSized` and avoid complicated behaviour change over an edition. +to be equivalent to `const ValueSized` and avoid complicated behaviour change over an edition. Without `Pointee`, if a user wanted to remove all sizedness bounds from a generic parameter then they would have two options: -1. Introduce new relaxed bounds (i.e. `?DynSized`), which has been found +1. Introduce new relaxed bounds (i.e. `?ValueSized`), which has been found unacceptable in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] summarizes these discussions) 2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound @@ -666,12 +666,12 @@ parameter then they would have two options: This is the only viable option, but this would complicate changing `size_of_val`'s existing `?Sized` bound: - Without `Pointee`, `?Sized` would be equivalent to `const DynSized` until + Without `Pointee`, `?Sized` would be equivalent to `const ValueSized` until extern types are stabilised (e.g. a `?Sized` bound would accept exactly the - same types as a `const DynSized` bound, but after extern types are introduced, - `?Sized` bounds would accept extern types and `const DynSized` bounds would not). + same types as a `const ValueSized` bound, but after extern types are introduced, + `?Sized` bounds would accept extern types and `const ValueSized` bounds would not). extern types would need to be introduced over an edition and all existing `?Sized` - bounds rewritten to `?Sized + const DynSized`. This is the same mechanism described + bounds rewritten to `?Sized + const ValueSized`. This is the same mechanism described in [rfcs#3396][rfc_extern_types_v2] to introduce its `MetaSized` trait. ## Why use const traits? @@ -686,19 +686,19 @@ runtime-sized types. [^5]: In previous iterations, the proposed linear trait hierarchy was: ``` - ┌─────────────────────────────────────────────────┐ - │ ┌─────────────────────────────────────┐ │ - │ │ ┌────────────────────────┐ │ │ - │ │ │ ┌───────┐ │ │ │ - │ │ │ │ Sized │ RuntimeSized │ DynSized │ Pointee │ - │ │ │ └───────┘ │ │ │ - │ │ └────────────────────────┘ │ │ - │ └─────────────────────────────────────┘ │ - └─────────────────────────────────────────────────┘ + ┌───────────────────────────────────────────────────┐ + │ ┌───────────────────────────────────────┐ │ + │ │ ┌────────────────────────┐ │ │ + │ │ │ ┌───────┐ │ │ │ + │ │ │ │ Sized │ RuntimeSized │ ValueSized │ Pointee │ + │ │ │ └───────┘ │ │ │ + │ │ └────────────────────────┘ │ │ + │ └───────────────────────────────────────┘ │ + └───────────────────────────────────────────────────┘ ``` This approach was scrapped once it became clear that a `const`-stable - `size_of_val` would need to be able to be instantiated with `DynSized` + `size_of_val` would need to be able to be instantiated with `ValueSized` types and not `RuntimeSized` types, and that this could not be represented. [^6]: In previous iterations, the proposed non-linear trait hierarchy was: @@ -711,7 +711,7 @@ runtime-sized types. │ │ │ │ Sized │ │ │ │ │ │ │ │ {type, target} │ │ │ │ │ │ │ └────────────────────────────┬┬─────────────────────┘ │ │ │ - │ │ │ RuntimeSized ││ DynSized │ │ │ + │ │ │ RuntimeSized ││ ValueSized │ │ │ │ │ │ {type, target, runtime env} ││ {type, target, value} │ │ │ │ │ └──────────────────────────────┘└───────────────────────┘ │ │ │ │ DynRuntimeSized │ │ @@ -724,7 +724,7 @@ runtime-sized types. This approach proposed modifying the bounds on the `size_of` function from `RuntimeSized` to `Sized` when used in a const context (and from - `DynRuntimeSized` to `DynSized` for `size_of_val`) to try and work + `DynRuntimeSized` to `ValueSized` for `size_of_val`) to try and work around the issues with constness, but this would have been unsound, and ultimately the inability to relax `Clone`'s supertrait made it infeasible anyway. @@ -763,7 +763,8 @@ ultimately need to be decided but aren't the important part of the RFC. There have been many previous proposals and discussions attempting to resolve the `size_of_val` and `align_of_val` for extern types through modifications to -the `Sized` trait, summarised below: +the `Sized` trait. Many of these proposals include a `DynSized` trait, of which +this RFC's `ValueSized` trait is inspired, just renamed. - [rfcs#709: truly unsized types][rfc_truly_unsized_types], [mzabaluev][author_mzabaluev], Jan 2015 - Earliest attempt to opt-out of `Sized`. @@ -914,17 +915,17 @@ the `Sized` trait, summarised below: - Adding new implicit bounds which can be relaxed has backwards compatibility hazards, see [rfcs#2255][issue_more_implicit_bounds]. - The proposed `DynSized` trait in [rfcs#2310][rfc_dynsized_without_dynsized] - is really quite similar to the trait proposed by this RFC except: + is really quite similar to the `ValueSized` trait proposed by this RFC except: - It includes an `#[assume_dyn_sized]` attribute to be added to - `T: ?Sized` bounds instead of replacing them with `T: const DynSized`, - which would warn instead of error when a non-`constage DynSized` type is + `T: ?Sized` bounds instead of replacing them with `T: const ValueSized`, + which would warn instead of error when a non-`const ValueSized` type is substituted into `T`. - This is to avoid a backwards compatibility break for uses of `size_of_val` and `align_of_val` with extern types, but it is unclear why this is necessary given that extern types are unstable. - It does not include `Pointee` or any of the const traits. - - Adding an explicit bound for `DynSized` does not remove the implicit + - Adding an explicit bound for `ValueSized` would not remove the implicit bound for `Sized`. - [rust#49708: `extern type` cannot support `size_of_val` and `align_of_val`][issue_extern_types_align_size], [joshtriplett][author_joshtriplett], Apr 2018 - Primary issue for the `size_of_val`/`align_of_val` extern types @@ -1047,6 +1048,7 @@ the `Sized` trait, summarised below: like in this RFC and proposes deprecating `T: ?Sized` in place of `T: Unsized` and sometimes `T: DynSized`. Adding a bound for any of `DynSized` or `Unsized` removes the default `Sized` bound. + - `DynSized` is the same as this RFC's `ValueSized` - `Unsized` is the same as this RFC's `Pointee` - As described below it is the closest inspiration for this RFC. @@ -1071,7 +1073,7 @@ is relatively newer and less well known: To summarise the above exhaustive listing of prior art: -- No previous works have proposed an equivalent of the const size traits., +- No previous works have proposed an equivalent of the const size traits. - One proposal proposed adding a marker type that as a field would result in the containing type no longer implementing `Sized`. - Often proposals focused at Custom DSTs preferred to combine the @@ -1103,7 +1105,7 @@ this proposal: removing `Sized` bounds when a bound for another sized trait (only `DynSized` in that pre-eRFC's case) was present, which makes reasoning simpler by avoiding relaxed bounds. - - However, this proposal had `size_of_val` methods its `DynSized` and + - However, this proposal had `size_of_val` methods in its `DynSized` trait and proposed a bunch of other things necessary for Custom DSTs. - [rfcs#2310: DynSized without ?DynSized][rfc_dynsized_without_dynsized] was proposed at a similar time and was similarly focused only on making `Sized` more @@ -1133,7 +1135,7 @@ None currently. other delineations in sized-ness that make sense to be drawn (subject to avoiding backwards-incompatibilities when changing APIs). - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2] could be added between - `Sized` and `DynSized` + `Sized` and `ValueSized` - The requirement that users cannot implement any of these traits could be relaxed in future if required. - Depending on a trait which has one of the proposed traits as a supertrait could @@ -1180,7 +1182,7 @@ support this by adding another supertrait, `Value`: │ │ ↓ ↓ ┌───────────────────────┐ ┌────────────────────────────────────┐ -│ const DynSized │ ──────────→ │ DynSized │ +│ const ValueSized │ ──────────→ │ ValueSized │ │ {type, target, value} │ implies │ {type, target, runtime env, value} │ └───────────────────────┘ └────────────────────────────────────┘ │ │ @@ -1211,7 +1213,7 @@ Earlier in this RFC, `extern type`s have previously been described as not being able to be used as a value, but it could instead be permitted to write functions which use extern types as values (e.g. such as taking an extern type as an argument), and instead rely on it being impossible to get a extern type that is not behind a -pointer or a reference. This also implies that `DynSized` types can be used as values, +pointer or a reference. This also implies that `ValueSized` types can be used as values, which would remain prohibited behind the `unsized_locals` and `unsized_fn_params` features until these are stabilised. @@ -1233,9 +1235,9 @@ used in `rustc` with the [`OpaqueListContents` type][rustc_opaquelistcontents] t implement a custom DST. This use case would break with this RFC as written, as the alignment of an extern type would correctly be undefined. -An `Aligned` trait could be added to this proposal between `DynSized` and `Pointee` +An `Aligned` trait could be added to this proposal between `ValueSized` and `Pointee` in the trait hierarchy which would be implemented automatically by the compiler for -all `DynSized` and `Sized` types, but could be implemented for extern types by +all `ValueSized` and `Sized` types, but could be implemented for extern types by users when the alignment of an extern type is known. Any type implementing `Aligned` could be used as the last element in a compound type. @@ -1253,7 +1255,7 @@ written here only to illustrate. - Allow `Pointee` to be implemented manually on user types, which would replace the compiler's implementation. - Introduce a trait like [rfcs#2594][rfc_custom_dst_electric_boogaloo]'s `Contiguous` - which users can implement on their custom DSTs, or add methods to `DynSized` and + which users can implement on their custom DSTs, or add methods to `ValueSized` and allow it to be implemented by users. - Introduce intrinsics which enable creation of pointers with metadata and for accessing the metadata of a pointer. From d2225162e27b0eddb042aaa8906ce91343b109c0 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 10:52:28 +0000 Subject: [PATCH 11/45] "statically known at runtime" -> "runtime constant" --- text/0000-sized-hierarchy.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index fb083f9d70f..89c91273111 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -7,10 +7,10 @@ [summary]: #summary All of Rust's types are either *sized*, which implement the `Sized` trait and -have a statically computable size during compilation, or *unsized*, which do not +have a statically known size during compilation, or *unsized*, which do not implement the `Sized` trait and are assumed to have a size which can be computed at runtime. However, this dichotomy misses two categories of type - types whose -size is unknown during compilation but statically known at runtime, and types +size is unknown during compilation but is a runtime constant, and types whose size can never be known. Supporting the former is a prerequisite to stable scalable vector types and supporting the latter is a prerequisite to unblocking extern types. @@ -72,10 +72,10 @@ Throughout the RFC, the following terminology will be used: and all of its supertraits but none of its subtraits. For example, a `ValueSized` type would be a type which implements `ValueSized`, and `Pointee`, but not `Sized`. `[usize]` would be referred to as a "`ValueSized` type". -- "Runtime-sized" types will be used those types whose size is statically known - but only at runtime. These would include the scalable vector types mentioned - in the motivation below, or those that implement `Sized` but not `const Sized` - in the RFC. +- "Runtime-sized" types will be used those types whose size is a runtime constant + and unknown at compilation time. These would include the scalable vector types + mentioned in the motivation below, or those that implement `Sized` but not + `const Sized` in the RFC. - The bounds on the generic parameters of a function may be referred to simply as the bounds on the function (e.g. "the caller's bounds"). @@ -102,15 +102,15 @@ CPU implementation, and the instructions which operate these registers are bit width-agnostic. As a consequence, these types are not `Sized` in the Rust sense, as the size of -a scalable vector cannot be known during compilation, but is statically known at -runtime. However, these are value types which should implement `Copy` and can be +a scalable vector cannot be known during compilation, but is a runtime constant. +However, these are value types which should implement `Copy` and can be returned from functions, can be variables on the stack, etc. These types should implement `Copy` but given that `Copy` is a supertrait of `Sized`, they cannot be `Copy` without being `Sized`, and aren't `Sized`. Introducing a `const Sized` trait will enable `Copy` to be implemented for -those types whose size is known statically only at runtime, function correctly -without special cases in the type system. See +those types whose size is a runtime constant to function correctly without +special cases in the type system. See [rfcs#3268: Scalable Vectors][rfc_scalable_vectors]. ## Unsized types and extern types @@ -145,8 +145,8 @@ Most types in Rust have a size known at compilation time, such as `u32` or For example, slices have an unknown length while compiling and are known as *dynamically-sized types*, their size must be computed at runtime. -There are also types with a statically known size but only at runtime, and -*unsized types* with no size whatsoever. +There are also types with a runtime constant size (but unknown at compile time), +and *unsized types* with no size whatsoever. Various parts of Rust depend on knowledge of the size of a type to work, for example: From 337c2e34ea288087aef63385a8c81d065b069997 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 11:01:21 +0000 Subject: [PATCH 12/45] elaborate on determining runtime-sized size --- text/0000-sized-hierarchy.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 89c91273111..d713d4142f8 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -97,16 +97,23 @@ with AVX-512, and Arm introduced 128-bit SIMD registers with Neon. As an alternative to releasing SIMD extensions with greater bit widths, Arm and RISC-V have vector extensions (SVE/Scalable Vector Extension and the "V" Vector -Extension respectively) where the bit width of vector registers depends on the +Extension/RVV respectively) where the bit width of vector registers depends on the CPU implementation, and the instructions which operate these registers are bit width-agnostic. As a consequence, these types are not `Sized` in the Rust sense, as the size of a scalable vector cannot be known during compilation, but is a runtime constant. -However, these are value types which should implement `Copy` and can be -returned from functions, can be variables on the stack, etc. These types should -implement `Copy` but given that `Copy` is a supertrait of `Sized`, they cannot -be `Copy` without being `Sized`, and aren't `Sized`. +For example, the size of these types could be determined by inspecting the value +in a register - this is not available at compilation time and the value may +differ between any given CPU implementation. Both SVE and RVV have mechanisms to +change the system's vector length (up to the maximum supported by the CPU +implementations) but this is not supported by the proposed ABI for these types. + +However, despite not implementing `Sized`, these are value types which should +implement `Copy` and can be returned from functions, can be variables on the +stack, etc. These types should implement `Copy` but given that `Copy` is a +supertrait of `Sized`, they cannot be `Copy` without being `Sized`, and +they aren't `Sized`. Introducing a `const Sized` trait will enable `Copy` to be implemented for those types whose size is a runtime constant to function correctly without From aa2a39b1b50826a01949523470d415099e2efcbb Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 11:08:36 +0000 Subject: [PATCH 13/45] const-stable size_of_val + ed. migration detail --- text/0000-sized-hierarchy.md | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index d713d4142f8..2886ac2fed1 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -464,8 +464,12 @@ the current implicit `Sized` bound to an `const Sized` bound as every type which exists currently will implement `const Sized`. **Edition change:** In the current edition, all existing `Sized` bounds will be -sugar for `const Sized`. In the next edition, existing `Sized` bounds will be rewritten -to `const Sized` bounds and new bare `Sized` bounds will be non-const. +syntatic sugar for `const Sized` (both explicitly written and implicit). In the next +edition, existing `Sized` bounds will be rewritten to `const Sized` bounds and new +bare `Sized` bounds will be non-const. `Sized` supertraits (such as that on `Clone` +or `Default`) will not be sugar for `const Sized`, these will remain bare `Sized`. +If traits with a `Sized` supertrait are later made const, then their supertrait +would be made `~const Sized`. An implicit `const ValueSized` bound is added to the `Self` type of traits. Like implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, `Sized`, @@ -497,17 +501,21 @@ This has the potential to break existing code like `uses_size_of` in the below example. However, due to the changes described in [`Sized` bounds][sized-bounds] (changing the implicit `T: Sized` to `T: const Sized`, and treating explicit `T: Sized` bounds as `T: const Sized` in the current edition), this code would -not break. +not break: ```rust= fn uses_size_of() -> usize { const { std::mem::size_of() } } + +fn another_use_of_size_of() -> [u8; size_of::()] { + std::array::repeat(0) +} ``` -[`size_of_val`][api_size_of_val] is currently const-unstable, so its bound can be -changed from `?Sized` to `~const ValueSized` without any backwards compatibility -issues. +[`size_of_val`][api_size_of_val] is also const-stable, so like `size_of` above, +its bound should be changed to `T: ~const Sized` and this would not result in any +breakage due to the previously described edition migration. ```rust= pub const fn size_of_val(val: &T) -> usize From 226ca5f26d28e8e5141a7d4eee475e79c8f706c3 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 11:33:48 +0000 Subject: [PATCH 14/45] remove const pointee --- text/0000-sized-hierarchy.md | 141 +++++++++++++++++------------------ 1 file changed, 67 insertions(+), 74 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 2886ac2fed1..0607a465dbc 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -173,35 +173,31 @@ example: Rust uses marker traits to indicate the necessary knowledge required to know the size of a type, if it can be known. There are three traits related to the size of a type in Rust: `Sized`, `ValueSized`, and the existing unstable -`std::ptr::Pointee`. Each of these traits can be implemented as `const` when the -size is knowable at compilation time. +`std::ptr::Pointee`. `Sized` and `ValueSized` can be implemented as `const` when +the size is knowable at compilation time. `Sized` is a supertrait of `ValueSized`, so every type which implements `Sized` also implements `ValueSized`. Likewise, `ValueSized` is a supertrait of `Pointee`. -`Sized` is `const` if-and-only-if `ValueSized` is `const`, and `ValueSized` is -`const` if-and-only-if `Pointee` is `const`. +`Sized` is `const` if-and-only-if `ValueSized` is `const`. ``` -┌──────────────────────────────────────────────────────────────────────────┐ -│ ┌──────────────────────────────────────────────────────────────────────┐ │ -│ │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ │ │ -│ │ ┃ ┌─────────────────────────────╂──────────────────────────────────┐ │ │ -│ │ ┃ │ ┏━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ │ │ │ -│ │ ┃ │ ┃ ┏━━━━━━━━━━━━━━━━━━━┓ ┃ ┃ │ │ │ -│ │ ┃ │ ┃ ┃ const Sized ┃ ┃ ┃ Sized │ │ │ -│ │ ┃ │ ┃ ┃ {type, target} ┃ ┃ ┃ {type, target, runtime env} │ │ │ -│ │ ┃ │ ┃ ┗━━━━━━━━━━━━━━━━━━━┛ ┃ ┃ │ │ │ -│ │ ┃ └─╂───────────────────────╂───╂──────────────────────────────────┘ │ │ -│ │ ┃ ┃ ┃ ┃ │ │ -│ │ ┃ ┃ const ValueSized ┃ ┃ ValueSized │ │ -│ │ ┃ ┃ {type, target, value} ┃ ┃ {type, target, runtime env, value} │ │ -│ │ ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ -│ └─╂───────────────────────────────╂────────────────────────────────────┘ │ -│ ┃ ┃ │ -│ ┃ const Pointee ┃ Pointee │ -│ ┃ {*} ┃ {*, runtime env} │ -│ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ │ -└──────────────────────────────────────────────────────────────────────────┘ +┌────────────────────────────────────────────────────────────────────┐ +│ ┌────────────────────────────────────────────────────────────────┐ │ +│ │ ┌────────────────────────────────────────────────────────────┐ │ │ +│ │ │ ┏━━━━━━━━━━━━━━━━━━━━━━━┓ │ │ │ +│ │ │ ┃ ┏━━━━━━━━━━━━━━━━━━━┓ ┃ │ │ │ +│ │ │ ┃ ┃ const Sized ┃ ┃ Sized │ │ │ +│ │ │ ┃ ┃ {type, target} ┃ ┃ {type, target, runtime env} │ │ │ +│ │ │ ┃ ┗━━━━━━━━━━━━━━━━━━━┛ ┃ │ │ │ +│ │ └─╂───────────────────────╂──────────────────────────────────┘ │ │ +│ │ ┃ ┃ │ │ +│ │ ┃ const ValueSized ┃ ValueSized │ │ +│ │ ┃ {type, target, value} ┃ {type, target, runtime env, value} │ │ +│ │ ┗━━━━━━━━━━━━━━━━━━━━━━━┛ │ │ +│ └────────────────────────────────────────────────────────────────┘ │ +│ Pointee │ +│ {*} │ +└────────────────────────────────────────────────────────────────────┘ ``` `const Sized` is implemented on types which require knowledge of only the @@ -234,13 +230,11 @@ to say, every type (put otherwise, these types may or may not be sized at all). For example, `Pointee` is therefore implemented on a `u32` which is trivially sized, a `[usize]` which is dynamically sized, a `svint8_t` which is runtime sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which has no -known size. `Pointee` is implemented as `const` when knowledge of the runtime -environment is not required (e.g. `const Pointee` for `u32` and `[usize]` but -bare `Pointee` for `svint8_t` or `[svint8_t]`). +known size. All type parameters have an implicit bound of `const Sized` which will be -automatically removed if a `Sized`, `const ValueSized`, `ValueSized`, -`const Pointee` or `Pointee` bound is present instead. +automatically removed if a `Sized`, `const ValueSized`, `ValueSized` or +`Pointee` bound is present instead. Prior to the introduction of `ValueSized` and `Pointee`, `Sized`'s implicit bound (now a `const Sized` implicit bound) could be removed using the `?Sized` syntax, @@ -251,7 +245,7 @@ which is now equivalent to a `ValueSized` bound in non-`const fn`s and [reference-level-explanation]: #reference-level-explanation Introduce a new marker trait, `ValueSized`, adding it to a trait hierarchy with the -[`Sized`][api_sized] and [`Pointee`][api_pointee] traits, and make all sizedness +[`Sized`][api_sized] and [`Pointee`][api_pointee] traits, and make `Sized` and `ValueSized` traits `const`: ``` @@ -267,14 +261,15 @@ traits `const`: │ const ValueSized │ ──────────→ │ ValueSized │ │ {type, target, value} │ implies │ {type, target, runtime env, value} │ └───────────────────────┘ └────────────────────────────────────┘ - │ │ - implies implies - │ │ - ↓ ↓ - ┌───────────────┐ ┌──────────────────┐ - │ const Pointee │ ──────────────────────→ │ Pointee │ - │ {*} │ implies │ {runtime env, *} │ - └───────────────┘ └──────────────────┘ + │ + implies + │ + ┌───────────────────────┘ + ↓ + ┌──────────────────┐ + │ Pointee │ + │ {runtime env, *} │ + └──────────────────┘ ``` Or, in Rust syntax: @@ -282,12 +277,12 @@ Or, in Rust syntax: ```rust= const trait Sized: ~const ValueSized {} -const trait ValueSized: ~const std::ptr::Pointee {} +const trait ValueSized: std::ptr::Pointee {} ``` `Pointee` was specified in [rfcs#2580][rfc_pointer_metadata_vtable] and is -currently unstable. `Pointee` would become a `const` trait. It could be moved -to `std::marker` alongside the other sizedness traits or kept in `std::ptr`. +currently unstable. It could be moved to `std::marker` alongside the other +sizedness traits or kept in `std::ptr`. ## Implementing `Sized` [implementing-sized]: #implementing-sized @@ -299,12 +294,9 @@ the compiler and cannot be implemented manually: - Types that which can be used from behind a pointer (they may or may not have a size). - `Pointee` will be implemented for: - - `ValueSized` types - - compound types where every element is `Pointee` - - `const Pointee` will be implemented for: - - `const ValueSized` types + - `ValueSized` and `const ValueSized` types - `extern type`s from [rfcs#1861][rfc_extern_types] - - compound types where every element is `const Pointee` + - compound types where every element is `Pointee` - In practice, every type will implement `Pointee` (as in [rfcs#2580][rfc_pointer_metadata_vtable]). - `ValueSized` @@ -405,16 +397,16 @@ a trait's associated type[^4] for the proposed traits. could be changed to one of the following bounds and remain compatible with any callers that currently exist (as per the above table): - | | `const Sized` | `Sized` | `const ValueSized` | `ValueSized` | `const Pointee` | `Pointee` - | -------------- | ------------- | ------- | ------------------ | ------------ | --------------- | --------- - | `const Sized` | ✔ | ✔ | ✔ | ✔ | ✔ | ✔ + | | `const Sized` | `Sized` | `const ValueSized` | `ValueSized` | `Pointee` + | -------------- | ------------- | ------- | ------------------ | ------------ | --------- + | `const Sized` | ✔ | ✔ | ✔ | ✔ | ✔ Likewise with a `T: ?Sized` bound: - | | `const ValueSized` | `ValueSized` | `const Pointee` | `Pointee` - | ------------------ | ------------------ | ------------ | --------------- | --------- - | `const Sized` | ✔ | ✔ | ✔ | ✔ - | `const ValueSized` | ✔ | ✔ | ✔ | ✔ + | | `const ValueSized` | `ValueSized` | `Pointee` + | ------------------ | ------------------ | ------------ | --------- + | `const Sized` | ✔ | ✔ | ✔ + | `const ValueSized` | ✔ | ✔ | ✔ [^4]: Associated types of traits have default `Sized` bounds which cannot be relaxed. For example, relaxing a `Sized` bound on `Add::Output` breaks a function which takes a `T: Add` and passes `::Output` to @@ -459,7 +451,7 @@ over an edition and all uses of it will be rewritten to a `const ValueSized` bou A default implicit bound of `const Sized` is added by the compiler to every type parameter `T` that does not have an explicit `Sized`, `?Sized`, `const ValueSized`, -`ValueSized`, `const Pointee` or `Pointee` bound. It is backwards compatible to change +`ValueSized` or `Pointee` bound. It is backwards compatible to change the current implicit `Sized` bound to an `const Sized` bound as every type which exists currently will implement `const Sized`. @@ -628,10 +620,10 @@ the standard library would need to be reviewed and updated as appropriate. - This is a fairly significant change to the `Sized` trait, which has been in the language since 1.0 and is now well-understood. - This RFC's proposal that adding a bound of `const Sized`, `const ValueSized`, - `ValueSized`, `const Pointee` or `Pointee` would remove the default `Sized` - bound is somewhat unintuitive. Typically adding a trait bound does not - remove another trait bound, however it's debatable whether this is more or - less confusing than existing `?Sized` bounds. + `ValueSized` or `Pointee` would remove the default `Sized` bound is somewhat + unintuitive. Typically adding a trait bound does not remove another trait bound, + however it's debatable whether this is more or less confusing than existing + `?Sized` bounds. - As this RFC depends on `const Trait`, it inherits all of the drawbacks of `const Trait`. @@ -1200,22 +1192,23 @@ support this by adding another supertrait, `Value`: │ const ValueSized │ ──────────→ │ ValueSized │ │ {type, target, value} │ implies │ {type, target, runtime env, value} │ └───────────────────────┘ └────────────────────────────────────┘ - │ │ - implies implies - │ │ - ↓ ↓ - ┌───────────────┐ ┌──────────────────┐ - │ const Pointee │ ──────────────────────→ │ Pointee │ - │ {*} │ implies │ {runtime env, *} │ - └───────────────┘ └──────────────────┘ - │ │ - implies implies - │ │ - ↓ ↓ - ┌─────────────┐ ┌──────────────────┐ - │ const Value │ ───────────────────────→ │ Value │ - │ {*} │ implies │ {runtime env, *} │ - └─────────────┘ └──────────────────┘ + │ + implies + │ + ┌───────────────────────┘ + ↓ + ┌──────────────────┐ + │ Pointee │ + │ {runtime env, *} │ + └──────────────────┘ + │ + implies + │ + ↓ + ┌──────────────────┐ + │ Value │ + │ {runtime env, *} │ + └──────────────────┘ ``` `Pointee` is still defined as being implemented for any type that can be used From a0f7bb7d00fbb46751fc69b0760252fd2700530a Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 11:50:13 +0000 Subject: [PATCH 15/45] elaborate on runtime-sized const prohibition --- text/0000-sized-hierarchy.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 0607a465dbc..cd50be3209f 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -168,7 +168,7 @@ example: its size and alignment, which extern types do not have. - For a value type to be allocated on the stack, it needs to have constant known size, which dynamically-sized and unsized types do not have (but - sized and "runtime sized" types do). + sized and "runtime-sized" types do). Rust uses marker traits to indicate the necessary knowledge required to know the size of a type, if it can be known. There are three traits related to the size @@ -228,9 +228,9 @@ to know the size of a `svint8_t`. `Pointee` is implemented by any type that can be used behind a pointer, which is to say, every type (put otherwise, these types may or may not be sized at all). For example, `Pointee` is therefore implemented on a `u32` which is trivially -sized, a `[usize]` which is dynamically sized, a `svint8_t` which is runtime -sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which has no -known size. +sized, a `[usize]` which is dynamically sized, a `svint8_t` which is +runtime-sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which has +no known size. All type parameters have an implicit bound of `const Sized` which will be automatically removed if a `Sized`, `const ValueSized`, `ValueSized` or @@ -736,6 +736,28 @@ runtime-sized types. and ultimately the inability to relax `Clone`'s supertrait made it infeasible anyway. +## Why change `size_of` and `size_of_val`? +[why-change-size_of-and-size_of_val]: #why-change-size_of-and-size_of_val + +It is theoretically possible for `size_of` and `size_of_val` to accept +runtime-sized types in a const context and use the runtime environment of the host +when computing the size of the types. This has some advantages, if implementable +within the compiler's interpreter, it could enable accelerated execution of +const code. However, there are multiple downsides to allowing this: + +Runtime-sized types are often platform specific and could require optional +target features, which would necessitate use of `cfg`s and `target_feature` +with const functions, adding a lot of complexity to const code. + +More importantly, the size of a runtime-sized type could differ between +the host and the target and if the size from a const context were to be used +at runtime with runtime-sized types, then that could result in incorrect +code. It is unintuitive that `const { size_of::() }` would not +be equal to `size_of::()`. + +Changing `size_of` and `size_of_val` to `~const Sized` bounds ensures that +`const { size_of:() }` is not possible. + ## Alternatives to this accepting this RFC [alternatives-to-this-rfc]: #alternatives-to-this-rfc From 2eae11e1037b319842ca2b41c46f045be0f44196 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 11:56:13 +0000 Subject: [PATCH 16/45] add acknowledgements section --- text/0000-sized-hierarchy.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index cd50be3209f..1b5475b0032 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -79,6 +79,18 @@ Throughout the RFC, the following terminology will be used: - The bounds on the generic parameters of a function may be referred to simply as the bounds on the function (e.g. "the caller's bounds"). +## Acknowledgements +[acknowledgements]: #acknowledgements + +This RFC wouldn't have been possible without the reviews and feedback of +[@JamieCunliffe][author_jamiecunliffe], [@JacobBramley][ack_jacobbramley], +[@nikomatsakis][author_nikomatsakis] and [@scottmcm][author_scottmcm]; +[@eddyb][ack_eddyb] for the `externref` future possibility; the expertise +of [@compiler-errors][ack_compiler_errors] on the type system and suggesting +the use of const traits; [@fee1-dead][ack_fee1dead] for reviewing the usage +of const traits and fixing the ASCII diagrams; and the authors of all of the +prior art for influencing these ideas. + # Motivation [motivation]: #motivation @@ -1290,6 +1302,11 @@ written here only to illustrate. - Introduce intrinsics which enable creation of pointers with metadata and for accessing the metadata of a pointer. +[ack_compiler_errors]: https://github.com/compiler-errors +[ack_eddyb]: https://github.com/eddyb +[ack_fee1dead]: https://github.com/fee1-dead +[ack_jacobbramley]: https://github.com/jacobbramley +[ack_scottmcm]: https://github.com/scottmcm [api_align_of]: https://doc.rust-lang.org/std/mem/fn.align_of.html [api_align_of_val]: https://doc.rust-lang.org/std/mem/fn.align_of_val.html [api_box]: https://doc.rust-lang.org/std/boxed/struct.Box.html From dcf2b848f87104506557653e94ebf853311a0bef Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 12:02:14 +0000 Subject: [PATCH 17/45] mention PhantomData --- text/0000-sized-hierarchy.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 1b5475b0032..558fc03207f 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -619,9 +619,13 @@ could be made to the standard library: - [`std::boxed::Box`][api_box] - `T: ?Sized` becomes `T: ValueSized` - - As before, this is not a breaking change and prevents types only - implementing `Pointee` from being used with `Box`, as these types do - not have the necessary size and alignment for allocation/deallocation. + - It is not a breaking change to relax this bound and it prevents types + only implementing `Pointee` from being used with `Box`, as these types + do not have the necessary size and alignment for allocation/deallocation. +- [`std::marker::PhantomData`][api_phantomdata] + - `T: ?Sized` becomes `T: Pointee` + - It is not a breaking change to relax this bound and there's no reason why + any type should not be able to be used with `PhantomData`. As part of the implementation of this RFC, each `Sized`/`?Sized` bound in the standard library would need to be reviewed and updated as appropriate. @@ -1310,12 +1314,13 @@ written here only to illustrate. [api_align_of]: https://doc.rust-lang.org/std/mem/fn.align_of.html [api_align_of_val]: https://doc.rust-lang.org/std/mem/fn.align_of_val.html [api_box]: https://doc.rust-lang.org/std/boxed/struct.Box.html -[api_copy]: https://doc.rust-lang.org/std/marker/trait.Copy.html [api_clone]: https://doc.rust-lang.org/std/clone/trait.Clone.html +[api_copy]: https://doc.rust-lang.org/std/marker/trait.Copy.html +[api_phantomdata]: https://doc.rust-lang.org/std/marker/struct.PhantomData.html [api_pointee]: https://doc.rust-lang.org/std/ptr/trait.Pointee.html -[api_sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html [api_size_of]: https://doc.rust-lang.org/std/mem/fn.size_of.html [api_size_of_val]: https://doc.rust-lang.org/std/mem/fn.size_of_val.html +[api_sized]: https://doc.rust-lang.org/std/marker/trait.Sized.html [author_aturon]: https://github.com/aturon [author_cad97]: https://github.com/CAD97 [author_canndrew]: https://github.com/canndrew @@ -1354,8 +1359,8 @@ written here only to illustrate. [pre_erfc_fix_dsts]: https://internals.rust-lang.org/t/pre-erfc-lets-fix-dsts/6663 [prerfc_custom_dst]: https://internals.rust-lang.org/t/pre-rfc-custom-dsts/8777 [rfc_aligned]: https://github.com/rust-lang/rfcs/pull/3319 -[rfc_custom_dst_electric_boogaloo]: https://github.com/rust-lang/rfcs/pull/2594 [rfc_custom_dst]: https://github.com/rust-lang/rfcs/pull/1524 +[rfc_custom_dst_electric_boogaloo]: https://github.com/rust-lang/rfcs/pull/2594 [rfc_dynsized_without_dynsized]: https://github.com/rust-lang/rfcs/pull/2310 [rfc_extern_types]: https://rust-lang.github.io/rfcs/1861-extern-types.html [rfc_extern_types_v2]: https://github.com/rust-lang/rfcs/pull/3396 From 487de4d1a5176c4d2dbfecfb4846c2ee18253343 Mon Sep 17 00:00:00 2001 From: David Wood Date: Thu, 14 Nov 2024 14:25:28 +0000 Subject: [PATCH 18/45] mention extension options --- text/0000-sized-hierarchy.md | 43 +++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/text/0000-sized-hierarchy.md b/text/0000-sized-hierarchy.md index 558fc03207f..210690ee37a 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/0000-sized-hierarchy.md @@ -359,6 +359,10 @@ would satisfy any relaxed bound[^3]. However, it would still be backwards-incompatible to relax the `Sized` bound on a trait's associated type[^4] for the proposed traits. +It is possible further extend this hierarchy in future by adding new traits between +those proposed in this RFC or after the traits proposed in this RFC without breaking +backwards compatibility, depending on the bounds that would be introduced[^5]. + [^1]: Adding a new automatically implemented trait and adding it as a bound to an existing function is backwards-incompatible with generic functions. Even though all types could implement the trait, existing generic functions will be @@ -443,6 +447,35 @@ a trait's associated type[^4] for the proposed traits. Relaxing the bounds of an associated type is in effect giving existing parameters a less restrictive bound which is not backwards compatible. +[^5]: If it was desirable to add a new trait to this RFC's proposed hierarchy + then there are four possibilities: + + - Before `Sized` + - i.e. `NewSized: Sized: ValueSized: Pointee` + - Adding bounds on a new trait before `Sized` is not + backwards-compatible as described above in [Implementing + `Sized`][implementing-sized]. + - Between `Sized` and `ValueSized` + - i.e. `Sized: NewSized: ValueSized: Pointee` + - Adding bounds on a new trait between `Sized` and `ValueSized` is + backwards compatible for some functions (except for on a trait's + associated type). An existing function with a `Sized` bound could + be relaxed to the new trait without breaking existing code. + Existing functions with a `ValueSized` or `Pointee` bound could not. + - Between `ValueSized` and `Pointee` + - i.e. `Sized: ValueSized: NewSized: Pointee` + - Adding bounds on a new trait between `ValueSized` and `Pointee` is + backwards compatible for some functions (except for on a trait's + associated type). An existing function with a `Sized` or `ValueSized` + bound could be relaxed to the new trait without breaking existing code. + Existing functions with a `Pointee` bound could not. + - After `Pointee` + - i.e. `Sized: ValueSized: Pointee: NewSized` + - For the same reasons as the traits proposed in this RFC, adding bounds + on a new trait after `Pointee` is backwards compatible (except for on a + trait's associated type). Any existing function will have stricter + bounds than the new trait and so could be relaxed to the new trait + without breaking existing code. ## `Sized` bounds [sized-bounds]: #sized-bounds @@ -700,13 +733,13 @@ parameter then they would have two options: ## Why use const traits? [why-use-const-traits]: #why-use-const-traits -Previous iterations of this RFC had both linear[^5] and non-linear[^6] trait hierarchies +Previous iterations of this RFC had both linear[^6] and non-linear[^7] trait hierarchies which included a `RuntimeSized` trait and did not use const traits. However, both of these were found to be backwards-incompatible due to being unable to relax the supertrait of `Clone`. Without const traits, it is not possible to represent runtime-sized types. -[^5]: In previous iterations, the proposed linear trait hierarchy was: +[^6]: In previous iterations, the proposed linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────┐ @@ -724,7 +757,7 @@ runtime-sized types. `size_of_val` would need to be able to be instantiated with `ValueSized` types and not `RuntimeSized` types, and that this could not be represented. -[^6]: In previous iterations, the proposed non-linear trait hierarchy was: +[^7]: In previous iterations, the proposed non-linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────────────────┐ @@ -1198,13 +1231,13 @@ None currently. Another compelling feature that requires extensions to Rust's sizedness traits to fully support is wasm's `externref`. `externref` types are opaque types that cannot -be put in memory [^7]. `externref`s are used as abstract handles to resources in the +be put in memory [^8]. `externref`s are used as abstract handles to resources in the host environment of the wasm program, such as a JavaScript object. Similarly, when targetting some GPU IRs (such as SPIR-V), there are types which are opaque handles to resources (such as textures) and these types, like wasm's `externref`, cannot be put in memory. -[^7]: When Rust is compiled to wasm, we can think of the memory of the Rust program +[^8]: When Rust is compiled to wasm, we can think of the memory of the Rust program as being backed by something like a `[u8]`, `externref`s exist outside of that `[u8]` and there is no way to put an `externref` into this memory, so it is impossible to have a reference or pointer to a `externref`. `wasm-bindgen` currently supports `externref` From 26f0aceabedf5bf17cd11ff26c829438e1659a5d Mon Sep 17 00:00:00 2001 From: David Wood Date: Fri, 15 Nov 2024 16:01:45 +0000 Subject: [PATCH 19/45] move to correct filename --- text/{0000-sized-hierarchy.md => 3729-sized-hierarchy.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename text/{0000-sized-hierarchy.md => 3729-sized-hierarchy.md} (99%) diff --git a/text/0000-sized-hierarchy.md b/text/3729-sized-hierarchy.md similarity index 99% rename from text/0000-sized-hierarchy.md rename to text/3729-sized-hierarchy.md index 210690ee37a..76e9584e58d 100644 --- a/text/0000-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -1,6 +1,6 @@ - Feature Name: `sized_hierarchy` - Start Date: 2024-09-30 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- RFC PR: [rust-lang/rfcs#3729](https://github.com/rust-lang/rfcs/pull/3729) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From a840d8b1563645d4e233c8ec5df5c2288ac3bfb0 Mon Sep 17 00:00:00 2001 From: David Wood Date: Fri, 15 Nov 2024 16:24:33 +0000 Subject: [PATCH 20/45] correct Copy is a subtrait of Sized MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: León Orell Valerian Liehr --- text/3729-sized-hierarchy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 76e9584e58d..472110623e8 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -123,8 +123,8 @@ implementations) but this is not supported by the proposed ABI for these types. However, despite not implementing `Sized`, these are value types which should implement `Copy` and can be returned from functions, can be variables on the -stack, etc. These types should implement `Copy` but given that `Copy` is a -supertrait of `Sized`, they cannot be `Copy` without being `Sized`, and +stack, etc. These types should implement `Copy` but given that `Sized` is a +supertrait of `Copy`, they cannot be `Copy` without being `Sized`, and they aren't `Sized`. Introducing a `const Sized` trait will enable `Copy` to be implemented for From 260c980f07f2eed12572d5ba3c25cfbb37e6698d Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:08:10 +0000 Subject: [PATCH 21/45] correct supertrait/subtrait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: León Orell Valerian Liehr --- text/3729-sized-hierarchy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 472110623e8..716ce598536 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -188,8 +188,8 @@ of a type in Rust: `Sized`, `ValueSized`, and the existing unstable `std::ptr::Pointee`. `Sized` and `ValueSized` can be implemented as `const` when the size is knowable at compilation time. -`Sized` is a supertrait of `ValueSized`, so every type which implements `Sized` -also implements `ValueSized`. Likewise, `ValueSized` is a supertrait of `Pointee`. +`Sized` is a subtrait of `ValueSized`, so every type which implements `Sized` +also implements `ValueSized`. Likewise, `ValueSized` is a subtrait of `Pointee`. `Sized` is `const` if-and-only-if `ValueSized` is `const`. ``` From 95a1b7ddadf2649288bcc71aecba4c776261add8 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:13:16 +0000 Subject: [PATCH 22/45] remove rust= from code blocks --- text/3729-sized-hierarchy.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 716ce598536..1458c2fa616 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -31,7 +31,7 @@ this bound. There are two functions in the standard library which can be used to get a size, [`std::mem::size_of`][api_size_of] and [`std::mem::size_of_val`][api_size_of_val]: -```rust= +```rust pub const fn size_of() -> usize { /* .. */ } @@ -286,7 +286,7 @@ traits `const`: Or, in Rust syntax: -```rust= +```rust const trait Sized: ~const ValueSized {} const trait ValueSized: std::ptr::Pointee {} @@ -372,7 +372,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. type, and it was added as a bound to `size_of` (or any other generic parameter).. - ```rust= + ```rust auto trait Foo; fn size_of() { /* .. */ } // `Foo` bound is new! @@ -380,7 +380,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. ...then user code would break: - ```rust= + ```rust fn do_stuff(value: T) { size_of(value) } // error! the trait bound `T: Foo` is not satisfied ``` @@ -390,7 +390,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. type, and it was added as a bound to `size_of_val` (or any other generic parameter).. - ```rust= + ```rust auto trait Foo; fn size_of_val(x: val) { /* .. */ } // `Foo` bound is new! @@ -432,7 +432,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. If a default `Sized` bound on an associated trait, such as `Add::Output`, were relaxed in the standard library... - ```rust= + ```rust trait Add { type Output: ValueSized; } @@ -528,7 +528,7 @@ could accept a runtime-sized type. It is therefore necessary to modify the bound `size_of` to accept a `T: ~const Sized`, so that `size_of` is a const function if-and-only-if `Sized` has a `const` implementation. -```rust= +```rust pub const fn size_of() -> usize { /* .. */ } @@ -540,7 +540,7 @@ example. However, due to the changes described in [`Sized` bounds][sized-bounds] `T: Sized` bounds as `T: const Sized` in the current edition), this code would not break: -```rust= +```rust fn uses_size_of() -> usize { const { std::mem::size_of() } } @@ -554,7 +554,7 @@ fn another_use_of_size_of() -> [u8; size_of::()] { its bound should be changed to `T: ~const Sized` and this would not result in any breakage due to the previously described edition migration. -```rust= +```rust pub const fn size_of_val(val: &T) -> usize where T: ~const ValueSized, From b670517d1eb1d5bd42076a91e8aadccf69352435 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:13:27 +0000 Subject: [PATCH 23/45] correct usage of const trait --- text/3729-sized-hierarchy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 1458c2fa616..2fc3361aa6d 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -127,7 +127,7 @@ stack, etc. These types should implement `Copy` but given that `Sized` is a supertrait of `Copy`, they cannot be `Copy` without being `Sized`, and they aren't `Sized`. -Introducing a `const Sized` trait will enable `Copy` to be implemented for +Making `Sized` a const trait will enable `Copy` to be implemented for those types whose size is a runtime constant to function correctly without special cases in the type system. See [rfcs#3268: Scalable Vectors][rfc_scalable_vectors]. From b8ea88152b9fc71ebba58330b17cceda570b57f3 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:13:59 +0000 Subject: [PATCH 24/45] mention context for dynamic stack allocation --- text/3729-sized-hierarchy.md | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 2fc3361aa6d..28ee5b23559 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -179,9 +179,13 @@ example: - Allocation and deallocation of an object with `Box` requires knowledge of its size and alignment, which extern types do not have. - For a value type to be allocated on the stack, it needs to have constant - known size, which dynamically-sized and unsized types do not have (but + known size[^1], which dynamically-sized and unsized types do not have (but sized and "runtime-sized" types do). +[^1]: Dynamic stack allocation does exist, such as in C's Variable Length Arrays + (VLA), but not in Rust (without incomplete features like `unsized_locals` + and `unsized_fn_params`). + Rust uses marker traits to indicate the necessary knowledge required to know the size of a type, if it can be known. There are three traits related to the size of a type in Rust: `Sized`, `ValueSized`, and the existing unstable @@ -347,23 +351,23 @@ the compiler and cannot be implemented manually: - anything else which currently implements `Sized` Introducing new automatically implemented traits is backwards-incompatible, -at least if you try to add it as a bound to an existing function[^1][^2] (and +at least if you try to add it as a bound to an existing function[^2][^3] (and new auto traits that which go unused aren't that useful), but due to being supertraits of `Sized` and `Sized` being a default bound, these backwards-incompatibilities are avoided for `ValueSized` and `Pointee`. Relaxing a bound from `Sized` to `ValueSized` or `Pointee` is non-breaking as the calling bound must have either `T: Sized` or `T: ?Sized`, both of which -would satisfy any relaxed bound[^3]. +would satisfy any relaxed bound[^4]. However, it would still be backwards-incompatible to relax the `Sized` bound on -a trait's associated type[^4] for the proposed traits. +a trait's associated type[^5] for the proposed traits. It is possible further extend this hierarchy in future by adding new traits between those proposed in this RFC or after the traits proposed in this RFC without breaking -backwards compatibility, depending on the bounds that would be introduced[^5]. +backwards compatibility, depending on the bounds that would be introduced[^6]. -[^1]: Adding a new automatically implemented trait and adding it as a bound to +[^2]: Adding a new automatically implemented trait and adding it as a bound to an existing function is backwards-incompatible with generic functions. Even though all types could implement the trait, existing generic functions will be missing the bound. @@ -384,7 +388,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. fn do_stuff(value: T) { size_of(value) } // error! the trait bound `T: Foo` is not satisfied ``` -[^2]: Trait objects passed by callers would not imply the new trait. +[^3]: Trait objects passed by callers would not imply the new trait. If `Foo` were introduced to the standard library and implemented on every type, and it was added as a bound to `size_of_val` (or any other generic @@ -402,7 +406,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. fn do_stuff(value: Box) { size_of_val(value) } // error! the trait bound `dyn Display: Foo` is not satisfied in `Box` ``` -[^3]: Callers of existing APIs will have one of the following `Sized` bounds: +[^4]: Callers of existing APIs will have one of the following `Sized` bounds: | Before ed. migration | After ed. migration | | --------------------------------- | ------------------- | @@ -423,7 +427,7 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. | ------------------ | ------------------ | ------------ | --------- | `const Sized` | ✔ | ✔ | ✔ | `const ValueSized` | ✔ | ✔ | ✔ -[^4]: Associated types of traits have default `Sized` bounds which cannot be +[^5]: Associated types of traits have default `Sized` bounds which cannot be relaxed. For example, relaxing a `Sized` bound on `Add::Output` breaks a function which takes a `T: Add` and passes `::Output` to `size_of` as not all types which implement the relaxed bound will @@ -440,14 +444,14 @@ backwards compatibility, depending on the bounds that would be introduced[^5]. ...then user code would break: - ```rust= + ```rust fn do_stuff() -> usize { std::mem::size_of::<::Output>() } //~^ error! the trait bound `::Output: Sized` is not satisfied ``` Relaxing the bounds of an associated type is in effect giving existing parameters a less restrictive bound which is not backwards compatible. -[^5]: If it was desirable to add a new trait to this RFC's proposed hierarchy +[^6]: If it was desirable to add a new trait to this RFC's proposed hierarchy then there are four possibilities: - Before `Sized` @@ -733,13 +737,13 @@ parameter then they would have two options: ## Why use const traits? [why-use-const-traits]: #why-use-const-traits -Previous iterations of this RFC had both linear[^6] and non-linear[^7] trait hierarchies +Previous iterations of this RFC had both linear[^7] and non-linear[^8] trait hierarchies which included a `RuntimeSized` trait and did not use const traits. However, both of these were found to be backwards-incompatible due to being unable to relax the supertrait of `Clone`. Without const traits, it is not possible to represent runtime-sized types. -[^6]: In previous iterations, the proposed linear trait hierarchy was: +[^7]: In previous iterations, the proposed linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────┐ @@ -757,7 +761,7 @@ runtime-sized types. `size_of_val` would need to be able to be instantiated with `ValueSized` types and not `RuntimeSized` types, and that this could not be represented. -[^7]: In previous iterations, the proposed non-linear trait hierarchy was: +[^8]: In previous iterations, the proposed non-linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────────────────┐ @@ -1231,13 +1235,13 @@ None currently. Another compelling feature that requires extensions to Rust's sizedness traits to fully support is wasm's `externref`. `externref` types are opaque types that cannot -be put in memory [^8]. `externref`s are used as abstract handles to resources in the +be put in memory [^9]. `externref`s are used as abstract handles to resources in the host environment of the wasm program, such as a JavaScript object. Similarly, when targetting some GPU IRs (such as SPIR-V), there are types which are opaque handles to resources (such as textures) and these types, like wasm's `externref`, cannot be put in memory. -[^8]: When Rust is compiled to wasm, we can think of the memory of the Rust program +[^9]: When Rust is compiled to wasm, we can think of the memory of the Rust program as being backed by something like a `[u8]`, `externref`s exist outside of that `[u8]` and there is no way to put an `externref` into this memory, so it is impossible to have a reference or pointer to a `externref`. `wasm-bindgen` currently supports `externref` From a703feb0a2b492a0416a2d9bdd86d6d95bdcffbc Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:15:46 +0000 Subject: [PATCH 25/45] use current const trait syntax --- text/3729-sized-hierarchy.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 28ee5b23559..4db7cc064bd 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -291,9 +291,11 @@ traits `const`: Or, in Rust syntax: ```rust -const trait Sized: ~const ValueSized {} +#![feature(const_trait_impl)] -const trait ValueSized: std::ptr::Pointee {} +#[const_trait] trait Sized: ~const ValueSized {} + +#[const_trait] trait ValueSized: std::ptr::Pointee {} ``` `Pointee` was specified in [rfcs#2580][rfc_pointer_metadata_vtable] and is From fad04b0a6ab386256cfe06667fb8941196e7cfd4 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:16:06 +0000 Subject: [PATCH 26/45] correct incorrect syntax --- text/3729-sized-hierarchy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 4db7cc064bd..87ff491d049 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -379,7 +379,7 @@ backwards compatibility, depending on the bounds that would be introduced[^6]. parameter).. ```rust - auto trait Foo; + auto trait Foo {} fn size_of() { /* .. */ } // `Foo` bound is new! ``` @@ -397,7 +397,7 @@ backwards compatibility, depending on the bounds that would be introduced[^6]. parameter).. ```rust - auto trait Foo; + auto trait Foo {} fn size_of_val(x: val) { /* .. */ } // `Foo` bound is new! ``` From 2fb3aaa63ab75bb37b707a4bf8a9003b34e47742 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:19:15 +0000 Subject: [PATCH 27/45] list all alternate bounds --- text/3729-sized-hierarchy.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 87ff491d049..e5bc3f62bb8 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -249,8 +249,8 @@ runtime-sized and an `extern type` (from [rfcs#1861][rfc_extern_types]) which ha no known size. All type parameters have an implicit bound of `const Sized` which will be -automatically removed if a `Sized`, `const ValueSized`, `ValueSized` or -`Pointee` bound is present instead. +automatically removed if a `~const Sized`, `Sized`, `const ValueSized`, +`~const ValueSized`, `ValueSized` or `Pointee` bound is present instead. Prior to the introduction of `ValueSized` and `Pointee`, `Sized`'s implicit bound (now a `const Sized` implicit bound) could be removed using the `?Sized` syntax, @@ -501,10 +501,10 @@ a `const ValueSized` bound. In the next edition, use of `?Sized` syntax will be over an edition and all uses of it will be rewritten to a `const ValueSized` bound. A default implicit bound of `const Sized` is added by the compiler to every type -parameter `T` that does not have an explicit `Sized`, `?Sized`, `const ValueSized`, -`ValueSized` or `Pointee` bound. It is backwards compatible to change -the current implicit `Sized` bound to an `const Sized` bound as every type which -exists currently will implement `const Sized`. +parameter `T` that does not have an explicit `~const Sized`, `Sized`, `?Sized`, +`const ValueSized`, `~const ValueSized`, `ValueSized` or `Pointee` bound. It is +backwards compatible to change the current implicit `Sized` bound to an `const Sized` +bound as every type which exists currently will implement `const Sized`. **Edition change:** In the current edition, all existing `Sized` bounds will be syntatic sugar for `const Sized` (both explicitly written and implicit). In the next From 23cb8a227e96452dc63eb78045f58bc259c66153 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:21:51 +0000 Subject: [PATCH 28/45] mention ?Trait being accepted for non-Sized --- text/3729-sized-hierarchy.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index e5bc3f62bb8..70ac7aa6e64 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -497,8 +497,11 @@ every type in Rust today which would satisfy a `?Sized` bound would satisfy a `const ValueSized` bound. **Edition change:** In the current edition,`?Sized` will be syntatic sugar for -a `const ValueSized` bound. In the next edition, use of `?Sized` syntax will be prohibited -over an edition and all uses of it will be rewritten to a `const ValueSized` bound. +a `const ValueSized` bound. As the `?Trait` syntax is currently accepted for any trait +but ignored for every trait except `Sized`, `?ValueSized` and `?Pointee` bounds would +be ignored. In the next edition, any uses of `?Sized` syntax will be rewritten to +a `const ValueSized` bound. Any other uses of the `?Trait` syntax will be removed as +part of the migration and the `?Trait` syntax will be prohibited. A default implicit bound of `const Sized` is added by the compiler to every type parameter `T` that does not have an explicit `~const Sized`, `Sized`, `?Sized`, From 0c2f6e6016919faa39792c97a6dca6c435213939 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:37:24 +0000 Subject: [PATCH 29/45] use distinct Pointee trait --- text/3729-sized-hierarchy.md | 48 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 70ac7aa6e64..2ebb75e9ddb 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -188,9 +188,9 @@ example: Rust uses marker traits to indicate the necessary knowledge required to know the size of a type, if it can be known. There are three traits related to the size -of a type in Rust: `Sized`, `ValueSized`, and the existing unstable -`std::ptr::Pointee`. `Sized` and `ValueSized` can be implemented as `const` when -the size is knowable at compilation time. +of a type in Rust: `Sized`, `ValueSized`, and `Pointee`. `Sized` and +`ValueSized` can be implemented as `const` when the size is knowable at compilation +time. `Sized` is a subtrait of `ValueSized`, so every type which implements `Sized` also implements `ValueSized`. Likewise, `ValueSized` is a subtrait of `Pointee`. @@ -260,8 +260,8 @@ which is now equivalent to a `ValueSized` bound in non-`const fn`s and # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -Introduce a new marker trait, `ValueSized`, adding it to a trait hierarchy with the -[`Sized`][api_sized] and [`Pointee`][api_pointee] traits, and make `Sized` and `ValueSized` +Introduce new marker traits, `ValueSized` and `Pointee`, adding it to a trait +hierarchy with the [`Sized`][api_sized] trait, and make `Sized` and `ValueSized` traits `const`: ``` @@ -295,12 +295,10 @@ Or, in Rust syntax: #[const_trait] trait Sized: ~const ValueSized {} -#[const_trait] trait ValueSized: std::ptr::Pointee {} -``` +#[const_trait] trait ValueSized: Pointee {} -`Pointee` was specified in [rfcs#2580][rfc_pointer_metadata_vtable] and is -currently unstable. It could be moved to `std::marker` alongside the other -sizedness traits or kept in `std::ptr`. +trait Pointee {} +``` ## Implementing `Sized` [implementing-sized]: #implementing-sized @@ -308,15 +306,14 @@ sizedness traits or kept in `std::ptr`. Implementations of the proposed traits are automatically generated by the compiler and cannot be implemented manually: -- [`Pointee`][api_pointee] +- `Pointee` - Types that which can be used from behind a pointer (they may or may not have a size). - `Pointee` will be implemented for: - `ValueSized` and `const ValueSized` types - `extern type`s from [rfcs#1861][rfc_extern_types] - compound types where every element is `Pointee` - - In practice, every type will implement `Pointee` (as in - [rfcs#2580][rfc_pointer_metadata_vtable]). + - In practice, every type will implement `Pointee`. - `ValueSized` - Types whose size is computable given a value, and knowledge of the type, target platform and runtime environment. @@ -715,7 +712,7 @@ There are various points of difference to the [prior art](#prior-art) related to ## Why have `Pointee`? [why-have-pointee]: #why-have-pointee -It may seem that re-using the `Pointee` trait at the bottom of the trait hierarchy +It may seem that introducing the `Pointee` trait at the bottom of the trait hierarchy is unnecessary as this is equivalent to the absense of any bounds whatsoever, but having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined to be equivalent to `const ValueSized` and avoid complicated behaviour change over an edition. @@ -739,6 +736,29 @@ parameter then they would have two options: bounds rewritten to `?Sized + const ValueSized`. This is the same mechanism described in [rfcs#3396][rfc_extern_types_v2] to introduce its `MetaSized` trait. +# Why not re-use `std::ptr::Pointee`? +[why-not-re-use-stdptrpointee]: #why-not-re-use-stdptrpointee + +`Pointee` is distinct from the existing unstable trait [`std::ptr::Pointee`][api_pointee] +from [rfcs#2580][rfc_pointer_metadata_vtable] as adding a trait with an associated item +to this hierarchy of traits would be backwards incompatible, breaking the example below +as it would be ambigious whether `T::Metadata` refers to `Pointee::Metadata` or +`HasMetadataAssocType::Metadata` and it is unambigious today. + +```rust +trait HasMetadataAssocType { + type Metadata; +} + +fn foo() -> T::Metadata { + todo!() +} +``` + +Instead of introducing a new marker trait, `std::ptr::Pointee` could be re-used if +there were some mechanism to indicate that `Pointee::Metadata` can only be referred +to with fully-qualified syntax. + ## Why use const traits? [why-use-const-traits]: #why-use-const-traits From 397e474a2cf81ed7d67e28d17797f8138c26c9f8 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:37:45 +0000 Subject: [PATCH 30/45] correct bound in size_of_val description --- text/3729-sized-hierarchy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 2ebb75e9ddb..35d74819af7 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -557,8 +557,8 @@ fn another_use_of_size_of() -> [u8; size_of::()] { ``` [`size_of_val`][api_size_of_val] is also const-stable, so like `size_of` above, -its bound should be changed to `T: ~const Sized` and this would not result in any -breakage due to the previously described edition migration. +its bound should be changed to `T: ~const ValueSized` and this would not result in +any breakage due to the previously described edition migration. ```rust pub const fn size_of_val(val: &T) -> usize From 6c1482bc9a4af453360da767310b5b5e2b52bf72 Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:52:22 +0000 Subject: [PATCH 31/45] update info about size_of_val and align_of_val --- text/3729-sized-hierarchy.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 35d74819af7..8b8ca7ed0c3 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -142,7 +142,7 @@ RFC #1861 defined that `std::mem::size_of_val` and `std::mem::align_of_val` should not be defined for extern types but not how this should be achieved, and suggested an initial implementation could panic when called with extern types, but this is always wrong, and not desirable behavior in keeping with Rust's -values. `size_of_val` returns 0 and `align_of_val` returns 1 for extern types in +values. `size_of_val` and `align_of_val` both panic for extern types in the current implementation. Ideally `size_of_val` and `align_of_val` would error if called with an extern type, but this cannot be expressed in the bounds of `size_of_val` and `align_of_val` and this remains a blocker for extern types. @@ -1338,11 +1338,6 @@ are examples of `Aligned` traits being added in the ecosystem: - [`unsized-vec`][crate_unsized_vec] implements a `Vec` that depends on knowing whether a type has an alignment or not. -Furthermore, the existing incorrect behaviour of `align_of_val` returning one is -used in `rustc` with the [`OpaqueListContents` type][rustc_opaquelistcontents] to -implement a custom DST. This use case would break with this RFC as written, as -the alignment of an extern type would correctly be undefined. - An `Aligned` trait could be added to this proposal between `ValueSized` and `Pointee` in the trait hierarchy which would be implemented automatically by the compiler for all `ValueSized` and `Sized` types, but could be implemented for extern types by From 1f85898896a85602d146049726f63f70ee303ffd Mon Sep 17 00:00:00 2001 From: David Wood Date: Sat, 16 Nov 2024 16:52:33 +0000 Subject: [PATCH 32/45] correct extern types in structs --- text/3729-sized-hierarchy.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 8b8ca7ed0c3..723ea89b678 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -147,10 +147,10 @@ the current implementation. Ideally `size_of_val` and `align_of_val` would error if called with an extern type, but this cannot be expressed in the bounds of `size_of_val` and `align_of_val` and this remains a blocker for extern types. -Furthermore, unsized types cannot be members of structs as their alignment is -unknown and this is necessary to calculate field offsets. Extern types also -cannot be used in `Box` as `Box` requires size and alignment for both allocation -and deallocation. +Furthermore, unsized types can only be the final member of structs as their +alignment is unknown and this is necessary to calculate the offsets of later fields. +Extern types also cannot be used in `Box` as `Box` requires size and alignment +for both allocation and deallocation. Introducing a hierarchy of `Sized` traits will enable the backwards-compatible introduction of a trait which only extern types do not implement and will From 5f4b8ff938f94a7f4ae7a76d5a84bc35a219b772 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 18 Nov 2024 14:01:09 +0000 Subject: [PATCH 33/45] elaborate on implicit relaxation --- text/3729-sized-hierarchy.md | 95 +++++++++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 723ea89b678..39d2ae1c074 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -521,8 +521,35 @@ implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, `Si As `ValueSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` for these traits. +### Summary of bounds changes +[summary-of-bounds-changes]: #summary-of-bounds-changes + +To summarise, in the current edition, the following bounds will be treated as +equivalent to: + +| Written | Interpretation | Notes | +| ------------------ | ------------------ | ------------------------------------------------------------------------------------ | +| `const Sized` | `const Sized` | | +| `Sized` | `const Sized` | Necessary for backwards compatibility, later rewritten to `const Sized` by `rustfix` | +| `?Sized` | `const ValueSized` | Necessary for backwards compatibility, later prohibited | +| `const ValueSized` | `const ValueSized` | | +| `ValueSized` | `ValueSized` | | +| `Pointee` | `Pointee` | | + +After the aforementioned edition migration, the following bounds will be +treated as equivalent to: + +| Written | Interpretation | +| ------------------ | ------------------ | +| `const Sized` | `const Sized` | +| `Sized` | `Sized` | +| `?Sized` | Prohibited | +| `const ValueSized` | `const ValueSized` | +| `ValueSized` | `ValueSized` | +| `Pointee` | `Pointee` | + ## `size_of` and `size_of_val` -[size-of-and-size-of-val]: #size-of-and-size-of-val +[size-of-and-size-of-val]: #size_of-and-size_of_val Runtime-sized types should be able to be passed to both `size_of` and `size_of_val`, but only at runtime, which requires ensuring these functions are only const if their @@ -571,8 +598,10 @@ where While `ValueSized` is equivalent to the current `?Sized` bound it replaces, it excludes extern types (which `?Sized` by definition cannot), which prevents -`size_of_val` from being called with extern types from -[rfcs#1861][rfc_extern_types]. +`size_of_val` from being called with extern types from [rfcs#1861][rfc_extern_types]. +Due to the changes described in [`Sized` bounds][sized-bounds] (migrating +`T: ?Sized` to `T: const ValueSized`), changing the bound of `size_of_val` will +not break any existing callers. These same changes apply to `align_of` and `align_of_val`. @@ -717,16 +746,14 @@ is unnecessary as this is equivalent to the absense of any bounds whatsoever, bu having an `Pointee` trait is necessary to enable the meaning of `?Sized` to be re-defined to be equivalent to `const ValueSized` and avoid complicated behaviour change over an edition. -Without `Pointee`, if a user wanted to remove all sizedness bounds from a generic -parameter then they would have two options: +Without introducing `Pointee`, if a user wanted to remove all sizedness bounds from a +generic parameter then they would have two options: -1. Introduce new relaxed bounds (i.e. `?ValueSized`), which has been found - unacceptable in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] - summarizes these discussions) -2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound - - This is the only viable option, but this would complicate changing - `size_of_val`'s existing `?Sized` bound: +1. Introduce a `?ValueSized` relaxed bound (a user could write `Sized`, `ValueSized` or + `?ValueSized`) which has been found unacceptable in previous RFCs + ([rfcs#2255][issue_more_implicit_bounds] summarizes these discussions). +2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound, which would + complicate changing `size_of_val`'s existing `?Sized` bound: Without `Pointee`, `?Sized` would be equivalent to `const ValueSized` until extern types are stabilised (e.g. a `?Sized` bound would accept exactly the @@ -736,6 +763,50 @@ parameter then they would have two options: bounds rewritten to `?Sized + const ValueSized`. This is the same mechanism described in [rfcs#3396][rfc_extern_types_v2] to introduce its `MetaSized` trait. +## Why migrate away from `?Sized`? +[why-migrate-away-from-sized]: #why-migrate-away-from-sized + +`?Sized` is frequently regarded as confusing for new users and came up frequently in +the [prior art][prior-art] as a reason why new `?Trait` bounds were not seen as desirable +(see [rfcs#2255][issue_more_implicit_bounds]). + +This RFC's proposal to migrate from `?Sized` is based on ideas from an [earlier +pre-eRFC][pre_erfc_fix_dsts] and then a [blog post][blog_dynsized_unsized] which developed +on those ideas, and the feedback to this RFC's prior art, but is not a load-bearing part +of this RFC. + +To preserve backwards compatibility, `Sized` bounds must be migrated to `const Sized` (see +[the `size_of` and `size_of_val` section][size_of-and-size_of_val] for rationale), but +`?Sized` bounds could retain their existing behaviour of removing the default `Sized` bound: + +In the current edition, `?Sized` would need to be equivalent to `?Sized + const ValueSized` to +maintain backwards compatibility (see [the `size_of` and `size_of_val` +section][size_of-and-size_of_val] for rationale). In the next edition, `?Sized` would be +rewritten to `?Sized + const ValueSized`. + +Prior to the edition migration, the default bound is `Sized`, which could be changed using +the following syntax: + +| With "implicit relaxation" | Keeping `?Sized` | +| -------------------------- | --------------------------- | +| `const Sized` | `?Sized + const Sized` | +| `const ValueSized` | `?Sized + const ValueSized` | +| `ValueSized` | `?Sized + ValueSized` | +| `Pointee` | `?Sized` | + +After the edition migration, the default bound is `const Sized`, which could be changed +using the following syntax: + +| With "implicit relaxation" | Keeping `?Sized` | +| -------------------------- | ------------------------------------------------------------------- | +| `Sized` | `?Sized + Sized` (maybe `?const Sized + Sized`?) | +| `const ValueSized` | `?Sized + const ValueSized` (or `?const Sized + const ValueSized`?) | +| `ValueSized` | `?Sized + ValueSized` (or `?const Sized + ValueSized`?) | +| `Pointee` | `?Sized` (or `?const Sized`?) | + +"Implicit relaxation" is used to refer to the behaviour that this RFC proposes where adding +any other sizedness bound removes the default bound. + # Why not re-use `std::ptr::Pointee`? [why-not-re-use-stdptrpointee]: #why-not-re-use-stdptrpointee From ab8e7f6476aa594c31bdfdb4a7ee56b609f6c153 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 09:44:02 +0000 Subject: [PATCH 34/45] further clarify ?Sized alternative --- text/3729-sized-hierarchy.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 39d2ae1c074..c34c5ef7064 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -782,31 +782,37 @@ To preserve backwards compatibility, `Sized` bounds must be migrated to `const S In the current edition, `?Sized` would need to be equivalent to `?Sized + const ValueSized` to maintain backwards compatibility (see [the `size_of` and `size_of_val` section][size_of-and-size_of_val] for rationale). In the next edition, `?Sized` would be -rewritten to `?Sized + const ValueSized`. +rewritten to `?Sized + const ValueSized` (remove the `Sized` default and add a `const ValueSized` +bound) and bare `?Sized` would only remove the `Sized` default bound. Prior to the edition migration, the default bound is `Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | -| -------------------------- | --------------------------- | -| `const Sized` | `?Sized + const Sized` | -| `const ValueSized` | `?Sized + const ValueSized` | -| `ValueSized` | `?Sized + ValueSized` | -| `Pointee` | `?Sized` | +| With "implicit relaxation" | Keeping `?Sized` | Notes | +| -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------ | +| `const Sized` | `?Sized + const Sized` | | +| `const ValueSized` | `?Sized` | `?Sized` is equivalent to `const ValueSized` for backwards compatibility | +| `ValueSized` | N/A | Not possible to write this, `?Sized` would remove the default `Sized` but add `const ValueSized` | +| `Pointee` | N/A | Not possible to write this, `?Sized` would remove the default `Sized` but add `const ValueSized` | After the edition migration, the default bound is `const Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | -| -------------------------- | ------------------------------------------------------------------- | -| `Sized` | `?Sized + Sized` (maybe `?const Sized + Sized`?) | -| `const ValueSized` | `?Sized + const ValueSized` (or `?const Sized + const ValueSized`?) | -| `ValueSized` | `?Sized + ValueSized` (or `?const Sized + ValueSized`?) | -| `Pointee` | `?Sized` (or `?const Sized`?) | +| With "implicit relaxation" | Keeping `?Sized` | Notes | +| -------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `Sized` | `?Sized + Sized` (maybe `?const Sized + Sized`?) | It isn't obvious how `?Sized` syntax should be used to remove the constness modifier | +| `const ValueSized` | `?Sized + const ValueSized` (or `?const Sized + const ValueSized`?) | | +| `ValueSized` | `?Sized + ValueSized` (or `?const Sized + ValueSized`?) | | +| `Pointee` | `?Sized` (or `?const Sized`?) | | "Implicit relaxation" is used to refer to the behaviour that this RFC proposes where adding any other sizedness bound removes the default bound. +Another alternative to "implicit relaxation" and `?Sized` could be to introduce new relaxed bounds +for `ValueSized` (i.e. `?ValueSized`), but this would only make sense if `ValueSized` were a default +bound (i.e. `T: Sized + ValueSized` was the default, rather than `T: Sized` which only implies +`T: Sized + ValueSized`). + # Why not re-use `std::ptr::Pointee`? [why-not-re-use-stdptrpointee]: #why-not-re-use-stdptrpointee From b050c2b48ebe6faeca0e612f4972afe8466151f0 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 09:44:28 +0000 Subject: [PATCH 35/45] reword references to relaxed bounds --- text/3729-sized-hierarchy.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index c34c5ef7064..51d823061e4 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -728,9 +728,9 @@ There are various points of difference to the [prior art](#prior-art) related to [rust#46108][pr_dynsized_rebase], [rfcs#2984][rfc_pointee_dynsized] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], none of the traits proposed in this RFC are default bounds and therefore do not - need to support being relaxed bounds (i.e. no `?ValueSized`), which avoids - additional language complexity and backwards compatibility hazards related to - relaxed bounds and associated types. + require additional relaxed bounds be accepted (i.e. no `?ValueSized`), which has + had mixed reception in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] + summarizes these discussions). - In contrast to [rfcs#1524][rfc_custom_dst], [rfc#1993][rfc_opaque_data_structs], [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts], [Pre-RFC: Custom DSTs][prerfc_custom_dst] and [eRFC: Minimal Custom DSTs via Extern Type (DynSized)][erfc_minimal_custom_dsts_via_extern_type], @@ -750,7 +750,7 @@ Without introducing `Pointee`, if a user wanted to remove all sizedness bounds f generic parameter then they would have two options: 1. Introduce a `?ValueSized` relaxed bound (a user could write `Sized`, `ValueSized` or - `?ValueSized`) which has been found unacceptable in previous RFCs + `?ValueSized`) which has had mixed reception in previous RFCs ([rfcs#2255][issue_more_implicit_bounds] summarizes these discussions). 2. Keep `?Sized`'s existing meaning of removing the implicit `Sized` bound, which would complicate changing `size_of_val`'s existing `?Sized` bound: @@ -766,9 +766,10 @@ generic parameter then they would have two options: ## Why migrate away from `?Sized`? [why-migrate-away-from-sized]: #why-migrate-away-from-sized -`?Sized` is frequently regarded as confusing for new users and came up frequently in -the [prior art][prior-art] as a reason why new `?Trait` bounds were not seen as desirable -(see [rfcs#2255][issue_more_implicit_bounds]). +`?Sized` is frequently regarded as confusing for new users and came up in the +[prior art][prior-art] as a reason why new `?Trait` bounds were not seen as desirable +(see [rfcs#2255][issue_more_implicit_bounds]). Furthermore, it isn't clear that `?Sized` +scales well to opting out of default bounds that have constness or hierarchies. This RFC's proposal to migrate from `?Sized` is based on ideas from an [earlier pre-eRFC][pre_erfc_fix_dsts] and then a [blog post][blog_dynsized_unsized] which developed @@ -1066,11 +1067,12 @@ this RFC's `ValueSized` trait is inspired, just renamed. - There have been various attempts to introduce new auto traits with implicit bounds, such as `DynSized`, `Move`, `Leak`, etc. Often rejected due to the ergonomic cost of relaxed bounds. - - `?Trait` being a negative feature confuses users. - - Downstream crates need to re-evaluate every API to determine if adding - `?Trait` makes sense, for each `?Trait` added. - - `?Trait` isn't actually backwards compatible like everyone thought due - to interactions with associated types. + - `?Trait` being a negative feature can be confusing to users. + - Depending on the specific trait being added as a default bound, downstream crates + need to re-evaluate every API to determine if adding `?Trait` makes sense, for + each `?Trait` added. + - `?Trait` isn't actually fully backwards compatible due to interactions with + associated types. - This thread was largely motivated by the `Move` trait and that was replaced by the `Pin` type, but there was an emerging consensus that `DynSized` may be more feasible due to its relationship with `Sized`. @@ -1287,8 +1289,7 @@ this proposal: - [Pre-eRFC: Let's fix DSTs][pre_erfc_fix_dsts] was the only other proposal removing `Sized` bounds when a bound for another sized trait (only `DynSized` - in that pre-eRFC's case) was present, which makes reasoning simpler by avoiding - relaxed bounds. + in that pre-eRFC's case) was present. - However, this proposal had `size_of_val` methods in its `DynSized` trait and proposed a bunch of other things necessary for Custom DSTs. - [rfcs#2310: DynSized without ?DynSized][rfc_dynsized_without_dynsized] was From 432a77aa18941bc3707b7eb20eb0413f0ae18ad1 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 09:52:17 +0000 Subject: [PATCH 36/45] strengthen drawback of implicit relaxation --- text/3729-sized-hierarchy.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 51d823061e4..c895f2bb9a0 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -84,7 +84,7 @@ Throughout the RFC, the following terminology will be used: This RFC wouldn't have been possible without the reviews and feedback of [@JamieCunliffe][author_jamiecunliffe], [@JacobBramley][ack_jacobbramley], -[@nikomatsakis][author_nikomatsakis] and [@scottmcm][author_scottmcm]; +[@nikomatsakis][author_nikomatsakis] and [@scottmcm][ack_scottmcm]; [@eddyb][ack_eddyb] for the `externref` future possibility; the expertise of [@compiler-errors][ack_compiler_errors] on the type system and suggesting the use of const traits; [@fee1-dead][ack_fee1dead] for reviewing the usage @@ -704,10 +704,11 @@ the standard library would need to be reviewed and updated as appropriate. - This is a fairly significant change to the `Sized` trait, which has been in the language since 1.0 and is now well-understood. - This RFC's proposal that adding a bound of `const Sized`, `const ValueSized`, - `ValueSized` or `Pointee` would remove the default `Sized` bound is somewhat - unintuitive. Typically adding a trait bound does not remove another trait bound, - however it's debatable whether this is more or less confusing than existing - `?Sized` bounds. + `ValueSized` or `Pointee` would remove the default `Sized` bound is a significant + change from the current `?Sized` mechanism. + - Typically adding a trait bound does not remove another trait bound, however + this RFC argues that this behaviour scales better to hierarchies of traits + with default bounds and constness. - As this RFC depends on `const Trait`, it inherits all of the drawbacks of `const Trait`. From 98af888300a66c0e25373c23077b9af139b06fc5 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 16:38:55 +0000 Subject: [PATCH 37/45] mention rfl want --- text/3729-sized-hierarchy.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index c895f2bb9a0..0fd1dc4c34b 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -137,6 +137,8 @@ special cases in the type system. See [Extern types][rfc_extern_types] has long been blocked on these types being neither `Sized` nor `?Sized` ([relevant issue][issue_extern_types_align_size]). +Extern types are listed as a "nice to have" feature in [Rust for Linux's requests +of the Rust project][rfl_want]. RFC #1861 defined that `std::mem::size_of_val` and `std::mem::align_of_val` should not be defined for extern types but not how this should be achieved, and @@ -1510,6 +1512,7 @@ written here only to illustrate. [rfc_truly_unsized_types]: https://github.com/rust-lang/rfcs/pull/709 [rfc_unsized_types]: https://github.com/japaric/rfcs/blob/unsized2/text/0000-unsized-types.md [rfc_virtual_structs]: https://github.com/rust-lang/rfcs/pull/5 +[rfl_want]: https://github.com/Rust-for-Linux/linux/issues/354 [rustc_aligned]: https://github.com/rust-lang/rust/blob/a76ec181fba25f9fe64999ec2ae84bdc393560f2/compiler/rustc_data_structures/src/aligned.rs#L22-L25 [rustc_opaquelistcontents]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/list/foreigntype.OpaqueListContents.html [zulip_issue_regions_too_simplistic]: https://rust-lang.zulipchat.com/#narrow/channel/144729-t-types/topic/.2321984.20.2B.20implicit.20supertraits.20-.20still.20relevant.3F/near/477630998 From 60eab1ac8b46479d61e791384f9130ef9ff87100 Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 16:46:38 +0000 Subject: [PATCH 38/45] weaken language around externref --- text/3729-sized-hierarchy.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 0fd1dc4c34b..34e34083448 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -1406,8 +1406,8 @@ pointer or a reference. This also implies that `ValueSized` types can be used as which would remain prohibited behind the `unsized_locals` and `unsized_fn_params` features until these are stabilised. -With these changes to the RFC, it would be possible to support wasm's `externref` and -opaque types from some GPU targets. +With these changes to the RFC and possibility additional changes to the language, it +could be possible to support wasm's `externref` and opaque types from some GPU targets. ## Alignment [alignment]: #alignment From 6e124e54a41680b580406eea04c887403010fa3c Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 18:28:56 +0000 Subject: [PATCH 39/45] further extend ?sized alternatives --- text/3729-sized-hierarchy.md | 71 ++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 34e34083448..4f9ed16d725 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -779,10 +779,19 @@ pre-eRFC][pre_erfc_fix_dsts] and then a [blog post][blog_dynsized_unsized] which on those ideas, and the feedback to this RFC's prior art, but is not a load-bearing part of this RFC. +In the following tables, "implicit relaxation" is used to refer to the behaviour that +this RFC proposes where adding any other sizedness bound removes the default bound. + To preserve backwards compatibility, `Sized` bounds must be migrated to `const Sized` (see [the `size_of` and `size_of_val` section][size_of-and-size_of_val] for rationale), but `?Sized` bounds could retain their existing behaviour of removing the default `Sized` bound: +### Keeping only `?Sized` +[keeping-only-sized]: #keeping-only-sized + +Without adding any additional default bounds or relaxed forms, keeping `?Sized` could be +compatible with this proposal as follows: + In the current edition, `?Sized` would need to be equivalent to `?Sized + const ValueSized` to maintain backwards compatibility (see [the `size_of` and `size_of_val` section][size_of-and-size_of_val] for rationale). In the next edition, `?Sized` would be @@ -792,30 +801,52 @@ bound) and bare `?Sized` would only remove the `Sized` default bound. Prior to the edition migration, the default bound is `Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | Notes | -| -------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------ | -| `const Sized` | `?Sized + const Sized` | | -| `const ValueSized` | `?Sized` | `?Sized` is equivalent to `const ValueSized` for backwards compatibility | -| `ValueSized` | N/A | Not possible to write this, `?Sized` would remove the default `Sized` but add `const ValueSized` | -| `Pointee` | N/A | Not possible to write this, `?Sized` would remove the default `Sized` but add `const ValueSized` | +| With "implicit relaxation" | Keeping `?Sized` | +| -------------------------- | ---------------- | +| `const Sized` | `const Sized` | +| `Sized` | `Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | Not possible | +| `Pointee` | Not possible | After the edition migration, the default bound is `const Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | Notes | -| -------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | -| `Sized` | `?Sized + Sized` (maybe `?const Sized + Sized`?) | It isn't obvious how `?Sized` syntax should be used to remove the constness modifier | -| `const ValueSized` | `?Sized + const ValueSized` (or `?const Sized + const ValueSized`?) | | -| `ValueSized` | `?Sized + ValueSized` (or `?const Sized + ValueSized`?) | | -| `Pointee` | `?Sized` (or `?const Sized`?) | | - -"Implicit relaxation" is used to refer to the behaviour that this RFC proposes where adding -any other sizedness bound removes the default bound. - -Another alternative to "implicit relaxation" and `?Sized` could be to introduce new relaxed bounds -for `ValueSized` (i.e. `?ValueSized`), but this would only make sense if `ValueSized` were a default -bound (i.e. `T: Sized + ValueSized` was the default, rather than `T: Sized` which only implies -`T: Sized + ValueSized`). +| With "implicit relaxation" | Keeping `?Sized` | +| -------------------------- | --------------------------------- | +| `const Sized` | `const Sized` | +| `Sized` | `?const Sized + Sized` | +| `const ValueSized` | `?const Sized + const ValueSized` | +| `ValueSized` | `?const Sized + ValueSized` | +| `Pointee` | `?const Sized` | + +### Adding `?ValueSized` +[adding-valuesized]: #adding-valuesized + +Another alternative is to make `ValueSized` a default bound in addition to `Sized` and establish +that relaxing a supertrait bound also implies relaxing subtrait bounds: + +Prior to the edition migration, the default bound is `ValueSized + const ValueSized + Sized`, +which could be changed using: + +| With "implicit relaxation" | Adding `?ValueSized` | +|----------------------------|----------------------------------| +| `const Sized` | `const Sized` | +| `Sized` | `Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | `?const ValueSized` | +| `Pointee` | `?ValueSized` | + +After the edition migration, the default bound is +`ValueSized + const ValueSized + Sized + const Sized`, which could be changed using: + +| With "implicit relaxation" | Adding `?ValueSized` | +|----------------------------|----------------------------------| +| `const Sized` | `const Sized` | +| `Sized` | `?const Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | `?const ValueSized` | +| `Pointee` | `?ValueSized` | # Why not re-use `std::ptr::Pointee`? [why-not-re-use-stdptrpointee]: #why-not-re-use-stdptrpointee From 4faeb83062dd3e4b26a56338f811cd27f2e2d6cc Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 19 Nov 2024 18:42:25 +0000 Subject: [PATCH 40/45] remove implicit relaxation terminology --- text/3729-sized-hierarchy.md | 59 +++++++++++++++++------------------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 4f9ed16d725..0a9bdec7e1c 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -779,9 +779,6 @@ pre-eRFC][pre_erfc_fix_dsts] and then a [blog post][blog_dynsized_unsized] which on those ideas, and the feedback to this RFC's prior art, but is not a load-bearing part of this RFC. -In the following tables, "implicit relaxation" is used to refer to the behaviour that -this RFC proposes where adding any other sizedness bound removes the default bound. - To preserve backwards compatibility, `Sized` bounds must be migrated to `const Sized` (see [the `size_of` and `size_of_val` section][size_of-and-size_of_val] for rationale), but `?Sized` bounds could retain their existing behaviour of removing the default `Sized` bound: @@ -801,24 +798,24 @@ bound) and bare `?Sized` would only remove the `Sized` default bound. Prior to the edition migration, the default bound is `Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | -| -------------------------- | ---------------- | -| `const Sized` | `const Sized` | -| `Sized` | `Sized` | -| `const ValueSized` | `?Sized` | -| `ValueSized` | Not possible | -| `Pointee` | Not possible | +| With positive bounds | Keeping `?Sized` | +| -------------------- | ---------------- | +| `const Sized` | `const Sized` | +| `Sized` | `Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | Not possible | +| `Pointee` | Not possible | After the edition migration, the default bound is `const Sized`, which could be changed using the following syntax: -| With "implicit relaxation" | Keeping `?Sized` | -| -------------------------- | --------------------------------- | -| `const Sized` | `const Sized` | -| `Sized` | `?const Sized + Sized` | -| `const ValueSized` | `?const Sized + const ValueSized` | -| `ValueSized` | `?const Sized + ValueSized` | -| `Pointee` | `?const Sized` | +| With positive bounds | Keeping `?Sized` | +| --------------------- | --------------------------------- | +| `const Sized` | `const Sized` | +| `Sized` | `?const Sized + Sized` | +| `const ValueSized` | `?const Sized + const ValueSized` | +| `ValueSized` | `?const Sized + ValueSized` | +| `Pointee` | `?const Sized` | ### Adding `?ValueSized` [adding-valuesized]: #adding-valuesized @@ -829,24 +826,24 @@ that relaxing a supertrait bound also implies relaxing subtrait bounds: Prior to the edition migration, the default bound is `ValueSized + const ValueSized + Sized`, which could be changed using: -| With "implicit relaxation" | Adding `?ValueSized` | -|----------------------------|----------------------------------| -| `const Sized` | `const Sized` | -| `Sized` | `Sized` | -| `const ValueSized` | `?Sized` | -| `ValueSized` | `?const ValueSized` | -| `Pointee` | `?ValueSized` | +| With positive bounds | Adding `?ValueSized` | +| --------------------- | -------------------------------- | +| `const Sized` | `const Sized` | +| `Sized` | `Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | `?const ValueSized` | +| `Pointee` | `?ValueSized` | After the edition migration, the default bound is `ValueSized + const ValueSized + Sized + const Sized`, which could be changed using: -| With "implicit relaxation" | Adding `?ValueSized` | -|----------------------------|----------------------------------| -| `const Sized` | `const Sized` | -| `Sized` | `?const Sized` | -| `const ValueSized` | `?Sized` | -| `ValueSized` | `?const ValueSized` | -| `Pointee` | `?ValueSized` | +| With positive bounds | Adding `?ValueSized` | +| -------------------- | -------------------------------- | +| `const Sized` | `const Sized` | +| `Sized` | `?const Sized` | +| `const ValueSized` | `?Sized` | +| `ValueSized` | `?const ValueSized` | +| `Pointee` | `?ValueSized` | # Why not re-use `std::ptr::Pointee`? [why-not-re-use-stdptrpointee]: #why-not-re-use-stdptrpointee From b4258c7cd29a55dd54dac8bcccc1840e36dfeb9b Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 25 Nov 2024 13:52:37 +0000 Subject: [PATCH 41/45] mention backwards incompatiblity with trait methods --- text/3729-sized-hierarchy.md | 51 +++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 0a9bdec7e1c..56af4b64f85 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -359,14 +359,15 @@ backwards-incompatibilities are avoided for `ValueSized` and `Pointee`. Relaxing a bound from `Sized` to `ValueSized` or `Pointee` is non-breaking as the calling bound must have either `T: Sized` or `T: ?Sized`, both of which -would satisfy any relaxed bound[^4]. +would satisfy any relaxed bound[^4]. -However, it would still be backwards-incompatible to relax the `Sized` bound on -a trait's associated type[^5] for the proposed traits. +However, it is not backwards compatible to relax the bounds of trait methods[^5] +and it would still be backwards-incompatible to relax the `Sized` bound on +a trait's associated type[^6] for the proposed traits. It is possible further extend this hierarchy in future by adding new traits between those proposed in this RFC or after the traits proposed in this RFC without breaking -backwards compatibility, depending on the bounds that would be introduced[^6]. +backwards compatibility, depending on the bounds that would be introduced[^7]. [^2]: Adding a new automatically implemented trait and adding it as a bound to an existing function is backwards-incompatible with generic functions. Even @@ -414,7 +415,7 @@ backwards compatibility, depending on the bounds that would be introduced[^6]. | `T: Sized` (implicit or explicit) | `T: const Sized` | | `T: ?Sized` | `T: const ValueSized` | - Any existing function in the standard library with a `T: Sized` bound + Any existing free function in the standard library with a `T: Sized` bound could be changed to one of the following bounds and remain compatible with any callers that currently exist (as per the above table): @@ -428,7 +429,29 @@ backwards compatibility, depending on the bounds that would be introduced[^6]. | ------------------ | ------------------ | ------------ | --------- | `const Sized` | ✔ | ✔ | ✔ | `const ValueSized` | ✔ | ✔ | ✔ -[^5]: Associated types of traits have default `Sized` bounds which cannot be +[^5]: In a crate defining a trait which has method with sizedness bounds, such + as... + + ```rust + trait Foo { + fn bar(t: T) -> usize; + fn baz(t: T) -> usize; + } + ``` + + ..then an implementor of `Foo` may rely on the existing bound and + these implementors of `Foo` would break if the bounds of `bar` or + `baz` were relaxed. + + ```rust + struct Qux; + + impl Foo for Qux { + fn bar(_: T) -> usize { std::mem::size_of } + fn baz(t: T) -> usize { std::mem::size_of_val(t) } + } + ``` +[^6]: Associated types of traits have default `Sized` bounds which cannot be relaxed. For example, relaxing a `Sized` bound on `Add::Output` breaks a function which takes a `T: Add` and passes `::Output` to `size_of` as not all types which implement the relaxed bound will @@ -452,7 +475,7 @@ backwards compatibility, depending on the bounds that would be introduced[^6]. Relaxing the bounds of an associated type is in effect giving existing parameters a less restrictive bound which is not backwards compatible. -[^6]: If it was desirable to add a new trait to this RFC's proposed hierarchy +[^7]: If it was desirable to add a new trait to this RFC's proposed hierarchy then there are four possibilities: - Before `Sized` @@ -871,13 +894,13 @@ to with fully-qualified syntax. ## Why use const traits? [why-use-const-traits]: #why-use-const-traits -Previous iterations of this RFC had both linear[^7] and non-linear[^8] trait hierarchies +Previous iterations of this RFC had both linear[^8] and non-linear[^9] trait hierarchies which included a `RuntimeSized` trait and did not use const traits. However, both of these were found to be backwards-incompatible due to being unable to relax the supertrait of `Clone`. Without const traits, it is not possible to represent runtime-sized types. -[^7]: In previous iterations, the proposed linear trait hierarchy was: +[^8]: In previous iterations, the proposed linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────┐ @@ -895,7 +918,7 @@ runtime-sized types. `size_of_val` would need to be able to be instantiated with `ValueSized` types and not `RuntimeSized` types, and that this could not be represented. -[^8]: In previous iterations, the proposed non-linear trait hierarchy was: +[^9]: In previous iterations, the proposed non-linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────────────────┐ @@ -1363,19 +1386,23 @@ None currently. - Consider allowing associated type bounds to be relaxed over an edition. - i.e. `type Output: if_rust_2021(Sized) + NewAutoTrait` or something like that, out of scope for this RFC. +- Consider allowing traits to relax their bounds and having their implementor have + stricter bounds - this would enable traits and implementations to migrate towards + more relaxed bounds. + - This would be unintuitive to callers but would not break existing code. ## externref [externref]: #externref Another compelling feature that requires extensions to Rust's sizedness traits to fully support is wasm's `externref`. `externref` types are opaque types that cannot -be put in memory [^9]. `externref`s are used as abstract handles to resources in the +be put in memory [^10]. `externref`s are used as abstract handles to resources in the host environment of the wasm program, such as a JavaScript object. Similarly, when targetting some GPU IRs (such as SPIR-V), there are types which are opaque handles to resources (such as textures) and these types, like wasm's `externref`, cannot be put in memory. -[^9]: When Rust is compiled to wasm, we can think of the memory of the Rust program +[^10]: When Rust is compiled to wasm, we can think of the memory of the Rust program as being backed by something like a `[u8]`, `externref`s exist outside of that `[u8]` and there is no way to put an `externref` into this memory, so it is impossible to have a reference or pointer to a `externref`. `wasm-bindgen` currently supports `externref` From 61f8d6dac96406a3f73f4adf11adce64ea3ec39c Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 25 Nov 2024 13:56:13 +0000 Subject: [PATCH 42/45] changed aligned future possibility --- text/3729-sized-hierarchy.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 56af4b64f85..3f587a88c3e 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -1474,11 +1474,10 @@ are examples of `Aligned` traits being added in the ecosystem: - [`unsized-vec`][crate_unsized_vec] implements a `Vec` that depends on knowing whether a type has an alignment or not. -An `Aligned` trait could be added to this proposal between `ValueSized` and `Pointee` -in the trait hierarchy which would be implemented automatically by the compiler for -all `ValueSized` and `Sized` types, but could be implemented for extern types by -users when the alignment of an extern type is known. Any type implementing `Aligned` -could be used as the last element in a compound type. +An `Aligned` trait hierarchy could be introduced alongside this proposal. It wouldn't +viable to introduce `Aligned` within this hierarchy, as `dyn Trait` which is `ValueSized` +would not be aligned, but some extern types could be `Aligned`, so there isn't an obvious +place that an `Aligned` trait could be included in this hierarchy. ## Custom DSTs [custom-dsts]: #custom-dsts From 9158ebb0aa3865d6475b3df741632693d3f1d574 Mon Sep 17 00:00:00 2001 From: David Wood Date: Mon, 25 Nov 2024 17:37:08 +0000 Subject: [PATCH 43/45] elaborate on implicit supertrait --- text/3729-sized-hierarchy.md | 38 ++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 3f587a88c3e..35e1aef1271 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -539,9 +539,31 @@ or `Default`) will not be sugar for `const Sized`, these will remain bare `Sized If traits with a `Sized` supertrait are later made const, then their supertrait would be made `~const Sized`. -An implicit `const ValueSized` bound is added to the `Self` type of traits. Like -implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, `Sized`, -`ValueSized` or `Pointee` bound is present. +An implicit `const ValueSized` bound is added to the `Self` type of traits to +avoid backwards incompatibility[^8]. Like implicit `const Sized` bounds, this is +omitted if an explicit `const Sized`, `Sized`, `ValueSized` or `Pointee` bound is +present. + +[^8]: If a user defines a subtrait of an existing trait, which has a method calling + a function with a `?Sized` bound (later a `const ValueSized` bound) instantiated + with `Self` then having no implicit `const ValueSized` bound on `Self` or + relaxing the implicit `const ValueSized` bound on `Self` could be a breaking + change. + + For example, if `std::io::Read` had no implicit `const ValueSized` bound on `Self` or + was relaxed to a `Pointee` bound on `Self`, then the following example would fail + to compile: + + ```rust + trait Sub: std::io::Read { + // Assume `std::mem::needs_drop`'s parameter remains `T: ?Sized` + // (or `const ValueSized`). + fn example() -> bool { std::mem::needs_drop::() } // breaks! + } + ``` + + Therefore, it is necessary that a default implicit `const ValueSized` supertrait + is added. As `ValueSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` for these traits. @@ -894,13 +916,13 @@ to with fully-qualified syntax. ## Why use const traits? [why-use-const-traits]: #why-use-const-traits -Previous iterations of this RFC had both linear[^8] and non-linear[^9] trait hierarchies +Previous iterations of this RFC had both linear[^9] and non-linear[^10] trait hierarchies which included a `RuntimeSized` trait and did not use const traits. However, both of these were found to be backwards-incompatible due to being unable to relax the supertrait of `Clone`. Without const traits, it is not possible to represent runtime-sized types. -[^8]: In previous iterations, the proposed linear trait hierarchy was: +[^9]: In previous iterations, the proposed linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────┐ @@ -918,7 +940,7 @@ runtime-sized types. `size_of_val` would need to be able to be instantiated with `ValueSized` types and not `RuntimeSized` types, and that this could not be represented. -[^9]: In previous iterations, the proposed non-linear trait hierarchy was: +[^10]: In previous iterations, the proposed non-linear trait hierarchy was: ``` ┌───────────────────────────────────────────────────────────────┐ @@ -1396,13 +1418,13 @@ None currently. Another compelling feature that requires extensions to Rust's sizedness traits to fully support is wasm's `externref`. `externref` types are opaque types that cannot -be put in memory [^10]. `externref`s are used as abstract handles to resources in the +be put in memory [^11]. `externref`s are used as abstract handles to resources in the host environment of the wasm program, such as a JavaScript object. Similarly, when targetting some GPU IRs (such as SPIR-V), there are types which are opaque handles to resources (such as textures) and these types, like wasm's `externref`, cannot be put in memory. -[^10]: When Rust is compiled to wasm, we can think of the memory of the Rust program +[^11]: When Rust is compiled to wasm, we can think of the memory of the Rust program as being backed by something like a `[u8]`, `externref`s exist outside of that `[u8]` and there is no way to put an `externref` into this memory, so it is impossible to have a reference or pointer to a `externref`. `wasm-bindgen` currently supports `externref` From b71401c383b33559884b1940ef669276bb8ff29c Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 26 Nov 2024 11:00:06 +0000 Subject: [PATCH 44/45] elaborate further on implicit supertraits --- text/3729-sized-hierarchy.md | 103 +++++++++++++++++++++++++++++++---- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 35e1aef1271..5f642cf4292 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -539,19 +539,17 @@ or `Default`) will not be sugar for `const Sized`, these will remain bare `Sized If traits with a `Sized` supertrait are later made const, then their supertrait would be made `~const Sized`. -An implicit `const ValueSized` bound is added to the `Self` type of traits to -avoid backwards incompatibility[^8]. Like implicit `const Sized` bounds, this is -omitted if an explicit `const Sized`, `Sized`, `ValueSized` or `Pointee` bound is -present. +An implicit `const ValueSized` supertrait to avoid backwards incompatibility[^8]. +Like implicit `const Sized` bounds, this is omitted if an explicit `const Sized`, +`Sized`, `ValueSized` or `Pointee` bound is present. [^8]: If a user defines a subtrait of an existing trait, which has a method calling a function with a `?Sized` bound (later a `const ValueSized` bound) instantiated - with `Self` then having no implicit `const ValueSized` bound on `Self` or - relaxing the implicit `const ValueSized` bound on `Self` could be a breaking - change. + with `Self` then having no implicit `const ValueSized` supertrait or relaxing + the implicit `const ValueSized` supertrait could be a breaking change. - For example, if `std::io::Read` had no implicit `const ValueSized` bound on `Self` or - was relaxed to a `Pointee` bound on `Self`, then the following example would fail + For example, if `std::io::Read` had no implicit `const ValueSized` supertrait or + was relaxed to a `Pointee` supertrait, then the following example would fail to compile: ```rust @@ -563,7 +561,92 @@ present. ``` Therefore, it is necessary that a default implicit `const ValueSized` supertrait - is added. + is added. This is the only circumstance which is a breaking change when no implicit + supertrait is added: + + If a `Sized` supertrait was added to any existing trait which is currently + without one, it would be a breaking change as that trait could be being + implemented on a trait which does not implement `Sized` - this is true + regardless of whether this RFC is accepted and this RFC's proposals have no + impact on this. + + As above, if a `const ValueSized` supertrait was added to an existing trait or + made the implicit supertrait then this would be equivalent to the existing + behaviour and not be a breaking change. + + If a `ValueSized` or `Pointee` supertrait were added to an existing trait or + made the implicit supertrait then this would not break any existing callers + using this trait - any parameters bound by this trait must also have either a + `?Sized`/`const ValueSized` bound or a `Sized` bound which would ensure any + existing uses of `size_of_val` (or other functions taking + `?Sized`/`const ValueSized`) continue to compile. + + In the below example, if an existing trait `Foo` continued to have no + supertrait, or had an `Pointee`, `ValueSized` or `const ValueSized` supertrait + added, then its uses would continue to compile: + + ```rust + trait Foo {} + // trait Foo: Pointee {} + // trait Foo: ValueSized {} + // trait Foo: const ValueSized {} + + // before migration.. + fn foo(t: &T) -> usize { size_of_val(t) } + fn foo_unsized(t: &T) -> usize { size_of_val(t) } + + // after migration.. + fn foo(t: &T) -> usize { size_of_val(t) } + fn foo_unsized(t: &T) -> usize { size_of_val(t) } + ``` + + After the proposed migration with a implicit `const ValueSized` supertrait, users + can write the following function, which would break if a supertrait were relaxed + to a `ValueSized` or `Pointee` supertrait, but no such function exists currently: + + ```rust + fn foo(t: &T) -> usize { size_of_val(t) } + ``` + + Implementors of traits in downstream crates would also not be broken - any + implementation of a trait with a newly added or relaxed supertrait on a downstream + type will require that the type implement `const ValueSized`, `ValueSized` or + `Pointee`, which it is guaranteed to do (as no types not implementing these traits + currently exist). + + ```rust + struct Local; + impl Foo for Local {} // not broken! + ``` + + Later, when types which do not implement `const ValueSized` or `ValueSized` + are available, then adding a supertrait to an existing trait could be a + breaking change. + + With the orphan rule, there can be no blanket impls of foreign traits, therefore + any implementation of a foreign trait will be on a local type. In this + circumstance, `Self` will refer to the concrete local type, which will implement + either `const Sized` or `const ValueSized` (as no other types currently exist), + and so any uses of `Self` which depend on it implementing a sizedness trait + will continue to compile even if the supertrait of the trait being implemented is + relaxed or there is no implicit supertrait. + + In a defining crate, a blanket impl on `T: ?Sized` would also not be broken as + the `?Sized` bound which would be migrated to `const ValueSized`. + + ```rust + trait Foo { + fn example(t: &Self) -> usize; + } + + impl Foo for T { + fn example(t: &Self) -> usize { std::mem::size_of_val(t) } + } + ``` + + In many of the cases above, relaxation of the supertrait is only guaranteed + to be backwards compatible in third party crates while there is no user code using + the new traits this proposal introduces. As `ValueSized` and `Pointee` are not default bounds, there is no equivalent to `?Sized` for these traits. From c9e71bd21099ea8e0d84cfdc60baabff6eec740b Mon Sep 17 00:00:00 2001 From: David Wood Date: Tue, 26 Nov 2024 11:28:43 +0000 Subject: [PATCH 45/45] add alternative about metasized/mutexes --- text/3729-sized-hierarchy.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/text/3729-sized-hierarchy.md b/text/3729-sized-hierarchy.md index 5f642cf4292..538d0cf44f6 100644 --- a/text/3729-sized-hierarchy.md +++ b/text/3729-sized-hierarchy.md @@ -1073,6 +1073,34 @@ be equal to `size_of::()`. Changing `size_of` and `size_of_val` to `~const Sized` bounds ensures that `const { size_of:() }` is not possible. +## What about `MetaSized` instead of or in addition to `ValueSized`? +[what-about-metasized-instead-of-or-in-addition-to-valuesized]: #what-about-metasized-instead-of-or-in-addition-to-valuesized + +`ValueSized` is defined as requiring a value of a type in order to compute +its size. An alternative or complement to `ValueSized` is `MetaSized`, first +proposed in [rfcs#3396][rfc_extern_types_v2], which requires inspecting +pointer metadata to compute the size. + +`ValueSized` has a downside that its interaction with mutexes introduces +the opportunity for deadlocks which are unintuitive: + +Consider a version of the `CStr` type which is a dynamically sized and +computes its size by counting the characters before the null byte (this +is different from the existing `std::ffi::CStr` which uses pointer metadata +like `MetaSized`). `CStr` would implement `ValueSized`. If this type were +used in a `Mutex` then the mutex would also implement `ValueSized` and +require locking itself to compute the size of the `CStr` that it guards, +which could result in unexpected deadlocks: + +```rust +let mutex = Mutex::new(CStr::from_str("foo")); +let _guard = mutex.lock().unwrap(); +size_of_val(&mutex); // deadlock! +``` + +`MetaSized` would avoid this hazard by keeping the size of dynamically sized +types in pointer metadata, which can be accessed without locking a mutex. + ## Alternatives to this accepting this RFC [alternatives-to-this-rfc]: #alternatives-to-this-rfc @@ -1478,8 +1506,6 @@ None currently. - Additional size traits could be added as supertraits of `Sized` if there are other delineations in sized-ness that make sense to be drawn (subject to avoiding backwards-incompatibilities when changing APIs). - - e.g. `MetaSized` from [rfcs#3396][rfc_extern_types_v2] could be added between - `Sized` and `ValueSized` - The requirement that users cannot implement any of these traits could be relaxed in future if required. - Depending on a trait which has one of the proposed traits as a supertrait could