owned this note
owned this note
Published
Linked with GitHub
---
title: "Design meeting 2025-09-10: Design sketches for more const generic types"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2025-09-10
discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202025-09-10.3A.20More.20const.20generic.20types/
url: https://hackmd.io/a4Ds5Ec0QlaOzPeyoPQE8w
---
# Design Sketches for More Const Generic Types
## Reading Guide
The document ["Complexities of Type System Values"](https://hackmd.io/@BoxyUwU/SJivShemgx) is useful background reading for this document and was covered in the previous lang design meeting.
There are a few concrete things I would like to get out of this meeting, listed sequentially in order of most to least important:
1. Would you be comfortable stabilizing the initial ADTs-only extensions?
- This would be properly RFC'd before stabilization, this ask is just a "vibe check".
2. Are you interested in seeing Per-Value Rejection for enums with undesirable variants?
3. How do you feel about the idea of Lossy Conversion as an approach in general, what about specifically for the References and Raw Pointers extensions?
4. How do you feel about the idea of dropping the One Equality ideal in general, what about specifically for `-0.0` vs `+0.0`, what about specifically for `NaN` values?
The first "decision" is by far the most important for me in this meeting.
If you find you're running out of time to read the whole document then the most important thing to read is the [Design Proposal][design_prop] section. Everything else is non-essential and only relevant for the "less important" decisions of the meeting.
## Terminology Recap
#### Const Generic Parameter
The definition site of const generics, i.e. `const N: Type` in generics. The struct `Foo` in `struct Foo<const N: usize>` has one Const Generic Parameter with the name `N` and has type `usize`.
#### Const Generic Argument
The "callsite" of const generics, i.e. an argument to a Const Generic Parameter. In `Foo<{ (u32::MAX + 1) as usize }>` the generic argument `{ (u32::MAX + 1 ) as usize }` is a Const Generic Argument.
#### Regular Value (RV)
The kind of value that you would encounter at runtime (or during const eval) that expressions evaluate to.
#### Type-System Value (TSV)
Also known as a ValTree. The way that values in the type-system are represented, for example the final value of a Const Generic Argument.
## Ideals Recap
The previous meeting document discussed a set of "ideals", things that we might like to be true about the design for supporting more types in Const Generics. They are briefly recapped here but this time alongside my own feelings about how much I value each of them:
1. Feel strongly we should not compromise this ideal:
- [Const Pats][ideal_const_pat], we shouldn't shut the door on being able to have uses of Const Generic Parameters as Const Patterns without post mono errors
- [Good Pattern Types][ideal_pat], we shouldn't shut the door on having good support for pattern types
2. Would like to uphold this ideal but it may be difficult to do so:
- [Semver][ideal_semver], supporting more types in Const Generics shouldn't result in changes that are unintuitive to be breaking
- [One Equality][ideal_equality], equality of TSVs should be equivalent to equality of RVs
- [Roundtripping][ideal_roundtrip], after undergoing a RV->TSV->RV roundtrip the final RV should be equivalent to the original RV
3. Doesn't matter much as even if it doesn't hold, it's still unlikely to cause problems:
- [Infallible RV->TSV][ideal_pmes], RV->TSV conversions should not be able to fail as it may introduce a new source of post-mono errors in the future. This ideal was previously called "No PMEs".
## Design Proposal
We will start with a simple extension to Const Generics of only adding support for ADTs. This doesn't give up on any of our ideals. Later we will discuss future possibilities.
### ADTs: Context
There are the following design considerations for struct and enums:
1. If new fields can be added backwards compatibly then there is a Semver pitfall of adding a new field with a type invalid for use in Const Generics
2. Forwards Compatibility with Lossy Conversion based extensions requires ADTs to not have safety invariants. This also generally worries me from a type inference POV and am unsure whether it would be possibly to accidentally infer values of fields that violate safety invariants.
3. ADTs that can't be constructed via a struct expression (e.g. due to private fields) could interact poorly with future support for using Generic Parameters in Const Generic Arguments.
4. Values of private fields matter for TSV equality which is weird for users.
### ADTs: Per-Type Reject, No Private Data
This is the initial design I want to stabilize. The rest is backwards compatible with this change.
Given points 2, 3, and 4 from above, we're not going to support private data.
To be forwards compatible with other designs and to support `#[non_exhaustive]` enums, we introduce an ADT definition-site opt-in to being useable in Const Generics. This opt-in recursively checks the types of all fields.
We reject structs and enums with a variant which is marked `#[non_exhaustive]` from being used in Const Generics. We also reject structs and enums with variants which have fields that are either `unsafe` or more private than the ADT itself (note that `unsafe` fields do not exist yet, and privacy on variant's fields is also not a thing).
Example of field privacy behaviour:
```rust
#[derive(ConstParamTy)]
pub struct Foo {
// ERROR: `field` is more private than `Foo`
field: u32
}
#[derive(ConstParamTy)]
pub struct Bar {
// OK: `field` is just as public as `Bar`
pub field: u32
}
// Hypothetical, privacy on enum fields is not a thing in Rust
#[derive(ConstParamTy)]
pub enum Qux {
// ERROR: `field` is more private than `Moo`
priv Variant { priv field: u32 }
}
```
Example of `#[non_exhaustive]` behaviour:
```rust
#[derive(ConstParamTy)]
pub enum Foo {
// ERROR: `#[non_exhaustive]` enum variants are not permitted in Const Generics
#[non_exhaustive]
Variant { field: u32 }
}
// OK: `Variant` is not `non_exhaustive` and we support `non_exhaustive` on enums
#[derive(ConstParamTy)]
#[non_exhaustive]
pub enum Bar {
Variant { field: u32 }
}
// ERROR: `Qux` is `non_exhaustive` which is not permitted in Const Generics
#[derive(ConstParamTy)]
#[non_exhaustive]
struct Qux {
pub field: u32,
}
```
Example of hypothetical `unsafe` fields behaviour:
```rust
#[derive(ConstParamTy)]
pub struct Foo {
// ERROR: `unsafe` fields are not permitted in Const Generics
pub unsafe field: u32,
}
#[derive(ConstParamTy)]
pub enum Bar {
Safe { field: u32 },
// ERROR: `unsafe` fields are not permitted in Const Generics
Unsafe { unsafe field: u32 },
}
```
We'll also support arrays and tuples :3 supporting them trivially falls out from our approach to ADTs.
## Future Extensions
The initial proposal is forwards compatible with all future extensions. We won't stabilize these right away as they have some potentially undesirable tradeoffs or compromise at least some of our ideals.
| Future Extensions | [One Equality][ideal_equality] | [Semver][ideal_semver] | [Roundtripping][ideal_roundtrip] | [Infallible RV->TSV][ideal_pmes] | [Const Pats][ideal_const_pat] | [Pattern Types][ideal_pat] |
| -------- | -------- | -------- | ------ | - | - | - |
| [ADTs Private Data][ext_adt_private] | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [ADTs (Per-Value Reject)][ext_adt_value] | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: |
| [References Extension][ext_ref] | :white_check_mark: | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| [Raw Pointers Extension][ext_ptr] | :white_check_mark: | :woman-shrugging: | :x: | :x: | :white_check_mark: | :white_check_mark: |
| [Floats Extension][ext_float] | .. | .. | .. | .. | .. | .. |
"ADTs Private Data" notably does not compromise on any ideals but does support private data, I consider this to be undesirable for now.
### ADTs: Private Data
We may want to support private fields and structs/enum-variants marked `#[non_exhaustive]`.
Supporting private data will require an ADT definition-site opt-in to avoid breaking safety invariants of the ADT due to type inference or lossy conversion. This also means we have to accept the general jank due to private fields, see the list in [ADTs: Context][adts_context].
### ADTs: Per-Value Reject
We could support enums in Const Generics as long as at least one of its variants would be valid. This would allow enums with invalid variants so long as the variants are not used.
This avoids the requirement for an ADT definition-site opt-in as long as we don't want the "Support Private Data" extension.
```rust
union NotValid {
a: u32,
b: u16,
}
enum MaybeValid {
NotValid(NotValid),
Valid(u32),
}
fn takes_const_arg<const ARG: MaybeValid>() {}
fn main() {
// ERROR: Using unions in Const Generics is forbidden
take_const_arg::<MaybeValid::NotValid(NotValid { a: 1 })>()
// OK: The other variant is totally fine
take_const_arg::<MaybeValid::Valid(1)>()
}
```
### ADTs: :ghost: Secret :ghost: Option
In theory there could be another option that is also "Per-Value Reject" and "Private Data" which does not require an opt-in on the ADT. It's exceedingly likely that this is not actually possible. It would require:
- Accept the exact same set of values in Const Generics as we do in Const Patterns to share the existing Semver issues from changing field types or adding new fields.
- This requires supporting References, Raw Pointers, and Floats.
- We must not use any Lossy Conversion as that requires us to disallow ADTs with safety invariants while supporting References and Raw Pointers.
- T-types being convinced (myself included) that supporting ADTs with safety invariants is *in general* okay, and we can't wind up with type inference inferring a Const Argument that violates a safety invariant of an ADT.
It still seems worth being forwards compatible with such a design as there *are* benefits to not requiring ADTs be opted into supporting Const Generics. The previous designs are all forwards compatible with this hypothetical design.
### References Extension
Reasons to support references in TSVs:
1. Existing types containing references can be used in Const Generics without having to define an equivalent type "modulo references"
2. Allows supporting slices and string slices in Const Generics without needing support for unsized types without indirection
- Note that array slices are less important as people can use arrays instead, but there is no "fixed size `str`"
3. It may wind up being useful for pattern types if TSVs can support references, it is unclear.
The design:
- RV->TSV conversion is lossy
- Padding in the pointee is turned into `uninit`
- Provenance is entirely disregarded, including whether the reference points to a static or not
I personally believe that it is not that important to support References in Const Generics *immediately*. The behaviour of References in Const Generics would be "weird" and we are lacking use cases in the first place. I would propose that we start by Per-Type Rejecting References which is forwards compatible with one day supporting this extension.
Regardless, here is a set of examples showcasing the behaviour of References under this extension:
Example of disregarding padding:
```rust
#![feature(adt_const_params, unsized_const_params)]
#[repr(C)]
#[derive(Eq, PartialEq, std::marker::ConstParamTy)]
struct Foo(u8, u16);
const ZEROES: [u16; 2] = [0; 2];
const REF: &Foo = unsafe { &*(&ZEROES as *const [u16; 2] as *const Foo) };
const fn rv_tsv_roundtrip<const N: &'static Foo>() -> &'static Foo {
N
}
fn main() {
const { unsafe {
let my_ref = &*(REF as *const Foo as *const [u8; 4]);
// OK: REF has the second byte of `Foo` initialised
my_ref[1];
} };
const { unsafe {
let my_ref = rv_tsv_roundtrip::<REF>();
let my_ref = &*(my_ref as *const Foo as *const [u8; 4]);
// UB: REF underwent a rv->tsv->rv roundtrip making
// the second byte of `Foo` be uninitialised
my_ref[1];
} };
}
```
Example of disregarding provenance:
```rust
#![feature(adt_const_params, unsized_const_params)]
const u32_SLICE_ELEM_REF: &u32 = &[1_u32, 2_u32][0];
const fn rv_tsv_roundtrip<const N: &'static u32>() -> &'static u32 {
N
}
fn main() {
// works
const { unsafe {
let my_ref = &*(u32_SLICE_ELEM_REF as *const u32 as *const [u32; 2]);
// OK: const eval preserved that u32_SLICE_ELEM_REF
// was derived from a reference to a larger allocation
my_ref[1]
} };
// doesn't work
const { unsafe {
let my_ref = rv_tsv_roundtrip::<u32_SLICE_ELEM_REF>();
let my_ref = &*(my_ref as *const u32 as *const [u32; 2]);
// UB: u32_SLICE_ELEM_REF underwent a rv->tsv->roundtrip
// making it only have provenance for the pointee type, `u32`
my_ref[1]
} };
}
```
Example of disregarding static identity:
```rust
#![feature(adt_const_params, unsized_const_params)]
static MY_STATIC: u32 = 10;
fn foo<const N: &'static u32>() -> usize {
N as *const u32 as usize
}
fn main() {
let addr = foo::<{ &MY_STATIC }>();
let addr_2 = (&MY_STATIC) as *const u32 as usize;
// MAY_PANIC: `&MY_STATIC` underwent a rv->tsv->rv roundtrip
// causing the reference to no longer be associated with
// any specific allocation/static
assert_eq!(addr, addr_2)
}
```
### Raw Pointers Extension
Reasons to support Raw Pointers in TSVs:
1. It may be useful to have pattern types with raw pointer base types of the form `(*mut u8) is const { ptr::with_addr(0x1) }`. i.e. the [Pattern Types][ideal_pat] ideal. There may be alternate solutions to satisfy this use case without supporting Raw Pointers in Const Generics.
2. It may be slightly more ergonomic to allow people to directly pass pointers to const generic parameters instead of having to explicitly cast to `usize` or some hypothetical `Address` type.
The design:
- Per-Value Reject Raw Pointers without a known address.
- Per-Value Reject fn-pointers transmuted to Raw Pointers.
I do not believe there to be a reasonable option for Lossy Conversion for either of those kinds of RVs.
In the future we may wind up with Raw Pointers with Provenance that *also* have a known address during const evaluation. This may arise from `ptr::with_exposed_provenance` or `my_usize as *const T`, though is not currently possible on stable. If we choose this solution then we are also commiting to the following *not yet observable choice*:
- Lossy Conversion of Raw Pointers with Provenance and a known address, to Raw Pointers without Provenance but with a known address
If we don't do this then `as` casts producing Raw Pointers with Provenance would be a breaking change.
I personally believe that it is not that important to support Raw Pointers in Const Generics *immediately*. The behaviour of Raw Pointers in Const Generics would be "weird" and we are lacking use cases in the first place. I would propose that we start by Per-Type Rejecting Raw Pointers which is forwards compatible with one day supporting this extension.
Regardless, here is a set of examples showcasing the behaviour of Raw Pointers under this extension. Unfortunately the current unstable implementation for supporting more types in Const Generics does not support Raw Pointers *at all*. This means that the following code examples can't actually be run with nightly rustc.
Example of RV->TSV errors from Raw Pointers without a known address:
```rust
const PTR: *const u32 = &10_u32 as *const u32;
fn takes_ptr<const N: *const u32>() {}
fn main() {
// ERROR: the rv->tsv conversion does not support
// RVs of Raw Pointers with Provenance.
takes_ptr::<PTR>();
}
```
Example of RV->TSV errors from fn-pointers transmuted to Raw Pointers:
```rust
fn foo() {}
const PTR: *const () = unsafe { std::mem::transmute(foo as fn()) };
fn takes_ptr<const N: *const ()> {}
fn main() {
// ERROR: the rv->tsv conversion does not support
// RVs of Function Pointers transmuted to Raw Pointers.
takes_ptr::<PTR>();
}
```
Example of disregarding provenance:
```rust
seems hard :( just describe in prose and make lang ask me questions?
- expose prov of an allocation
- from exposed provenance with hardcoded address
- RV->TSV->RV roundtrip of that pointer
- offset the pointer at runtime to "correct" address
- read from the pointer and get UB
```
### Floats Extension
There are generally two options for supporting floats in Const Generics. One maintains the One Equality ideal the other does not. Briefly summarized:
- Break One Equality ideal, -0.0 and +0.0 are supported in TSVs as unequal, NaNs are supported as equal to eachother if the bitpatterns are the same
- optionally we could Lossily Convert all NaNs to the same bitpattern
- Maintain One Equality ideal, Per-Value Reject Nans, Lossily Convert -0.0 to +0.0 or vice versa
I personally believe that we don't yet have enough information about use cases for floats in Const Generics to decide between either design. I would like to defer support for floats to the future and Per-Type Reject them for now. For more details about the different options, see [Summary: Floats](https://hackmd.io/@BoxyUwU/SJivShemgx#Floats90).
[adts_context]: #ADTs-Context
[ext_adt_private]: #ADTs-Private-Data
[ext_adt_value]: #ADTs-Per-Value-Reject
[ext_adt_type]: #ADTs-Per-Type-Reject-No-Private-Data
[ext_ref]: #References-Extension
[ext_ptr]: #Raw-Pointers-Extension
[ext_float]: #Floats-Extension
[design_prop]: #Design-Proposal
[ideal_roundtrip]: https://hackmd.io/@BoxyUwU/SJivShemgx#Lossless-Roundtripping
[ideal_pmes]: https://hackmd.io/@BoxyUwU/SJivShemgx#No-Post-mono-Errors
[ideal_equality]: https://hackmd.io/@BoxyUwU/SJivShemgx#One-Equality
[ideal_semver]: https://hackmd.io/@BoxyUwU/SJivShemgx#Intuitive-Semver
[ideal_const_pat]: https://hackmd.io/@BoxyUwU/SJivShemgx#Const-Patterns-Parity
[ideal_pat]: https://hackmd.io/@BoxyUwU/SJivShemgx#Good-Pattern-Types
---
# Discussion
## Attendance
- People TC, Boxy, Tomas Sedovic, Niko, Tyler, Eric Holk, Yosh, scottmcm
## Meeting roles
- Driver: TC
- Minutes: Tomas Sedovic
## Recap by boxy
The main thing is vibe check about stabilising ADT and no private data supported. People in general seems okay with ADTs and a bit more hesitant about private data.
## Vibe check
The main ask:
> Would you be comfortable stabilizing the initial ADTs-only extensions?
(plus the other ones)
### nikomatsakis
I am +1 on working incrementally and focusing first on ADTs. I am supportive of stabilization overall but I don't feel like we've "nailed" the way to talk or think about these things. So I guess my "vibe" is +1 but if this doc were turned into an RFC kind of "as is" I would probably wind up -1 on the RFC, I think more work is needed (in some sense, the question is, "what is the name of the opt-in trait and why is it named that"). This space is complex and I think we have to do better at helping people understand the fine-grained distinctions between runtime values, const-eval values, and type-safe values.
Niko: if we add some sort of derive of a trait name, how much value are we getting from the derive, what should the trait be named?
### tmandry
I think we'll learn the most by stabilizing ADTs in a forward compatible way (including an opt-in) now. So +1 from me on the proposed design.
It's worth noting that this is a feature that interacts with many other features, and we will be considering extensions to the MVP for the foreseeable future. To some extent the lang team has committed to this already but we should know what we're signing ourselves up for.
### scottmcm
scottmcm: concern over the private fields restriction (see question below), but otherwise for the top ask, yes happy to just do "simple" types (no floats, no cells, no references, etc).
### TC
As Niko said, +1 on working incrementally, and I too am supportive overall.
As a vibe, per-value rejection seems fairly OK to me in that we decided to do value-based reasoning for other const checks. It occurs to me there's some parallel with that.
https://github.com/rust-lang/rust/pull/119044
As for the opt-in on types, I see the logic. I do have reservations about adding too many opt-ins to the language, and so I'm curious about whether this can be safely removed.
Regarding floats, I see the question on these as related to our decision about how to handle padding in structs. If it makes sense to normalize or otherwise treat `-0.0` and `+0.0` as the same, then it'd also make sense in my view to normalize or otherwise treat two structs with the same values but different padding (or where only one has initialized padding) as the same.
## What is the role of the opt-in for structs
nikomatsakis: The document says
> To be forwards compatible with other designs and to support `#[non_exhaustive]` enums, we introduce an ADT definition-site opt-in to being useable in Const Generics. This opt-in recursively checks the types of all fields.
My question is, if we say that a type is "const compatible" if it is
* a scalar value like ints or whatever that we already support
* a struct or enum that is (a) exhaustive (i.e., not `#[non_exhaustive]`) and (b) fully public
then I think that there are no changes that this type could make that are semver compatible which also break code using const, is that correct? If so, does the "opt-in" still serve any sort of role?
Niko: I think if you opt-in, in the const-fn you can only instantiate this with typesafe values? What is the impact.
Boxy: In the initial design: two primary reasons: 1. it makes it forwards compatibel with the other extensions, 2. you said if we don't allow ADTs that are defined nonsexhaustibely is true. But having the opt-in allows us to have non_exhaustive enums.
Tyler: You're publicly declaring you won't change the type or add fields that make it incompatible with the derive in the future.
Niko: What would be a change you could no longer make?
Tyler: Adding a private field
Niko: you can't add a private field -- that's semver incompatible.
Tyler: If we have a type with a private field then we can add more private fields.
Niko: You can lean on negative reasoning for the stuff you own, that's ok to do. Maybe a similar approach can be used here.
Tyler: Maybe requiring an opt-in for people outside of the crait to rely on the fact that it can be used for const generics but not require it inside the crate.
TC: There are other semver breaking changes like adding a private field that is not `const drop`.
Niko: What Tyler said makes sense to me. I may have a const fn that I'll never use in const generic but someone else may chose to.
Scott: Is this similar to non_exaustive-like restriction. You as the crate owner could use it in const generic but users of the crate have to have the derive.
TC: One could similarly imagine needing to opt-in a type because you're promising that you won't later add a private field that isn't `const Drop` (and will therefore break anyone who has dropped a value of the type in a const context).
Niko: This is a great area in Rust. Changing your drop glue can be semver breaking. It can also borrow check for example. You could think about that more as a way in which drop breaks semver rather than a const issue.
Niko: I don't think treating it like non-exhaustive makes sense here. There we're making the type less useful for your consumer because they can't have matches that they know they're exhaustive but there's no reason for you to have the same restriction.
Boxy: It's not just about the semver of adding new fields not being able to use in const generics. That is one case but that's also solved by only supporting ADTs with no private fields. But also: some of the extensions like references, pointers, floats can do sneaky things that change values. If we have ADTs that have these fields but this lossy stuff would break. The initial design proposal doesn't require this because everything is public but if we were ot support private data this would become a concern.
Tyler: How does the situation arise where you have a type with a safety invariant and we produce code that violates it somehow?
Niko: You can have a struct which e.g. leverages the -0.0 / + 0.0 distinction and then if we normalize it to always be +0.0 it'll always do one thing.
Boxy: That's correct. If all the fields are public, that's fine.
Boxy: Another reason for opt-in: this design doesn't support uses of generic parameters in const arguments yet. Supporting that kind of code is very difficult. And it's significantly easier to support struct expressions than arbitrary function calls. So we may one day one wind up with an extension that only support struct expressions.
Scott: If we end up where the only thing you can put in there is a struct literal, we end up where passing struct fields is passing multiple parameters parameters. Is your concern about layout being dificult or functions being difficult?
Boxy: Functions in particular are difficult.
Scott: If we ended up in a world where the only way you could pass structs with all public fields and no functions I question how useful that might be. I'd like it to be more than that at least.
Tyler: What do you mean passing functions?
Scott: Calling a function to generate the value to pass in to the const generic argument.
Tyler: I agree that would be useful.
Boxy: It doesn't always have to be a struct literal. But if you have to use ??
Scott: This is then: yes you could pass a complex type, it would just have to be something you can write in a non-associated const item.
Boxy: Yeah, exactly.
Tyler: What exactly do we supprot in the MVP? The notes say it doesn't support use of generic parameters in the const arguments yet.
```rust
const fn foo<const N: usize>() {
bar::<N>()
}
```
Boxy: This is referencing the existing limitation of const generics. You can either forward parameters as-is as arguments or compute arguments in a way that doesn't reference any parameters.
Boxy: All arguments to const parameters must be something you could write in a const item that does not have any generics.
Niko: Boxy, what do you promisisng not to do when you derive the trait?
Boxy: In the initial stabilisation?
Niko: Initial.
Boxy: The only real purpose is if you have a non_exhaustive enum, any items you add in the future won't have ??
## Private really seems important
scottmcm: the obvious first cases I'd want to use this with *do* have private fields: things like `alloc::Layout` and `ptr::Alignment`. Doesn't the fact that I'm implementing `ConstParamTy` for the type mean that I'm promising not to add things in the future that couldn't support that? (I'd also be fine with having, say, `const struct Layout` or something if we'd need that to do good checking on it.)
scottmcm: As specific examples of that, there's a ton of manual polymorphization stuff in the library that wants `Layout`. For example `mem::swap<T>` being a shim that calls `swap_inner::<{Layout::new::<T>()}>` would be a huge help for code size (we could essentially always MIR-inline the outer one, avoiding the need to mono per-type for stuff like this). Similarly we're doing dynamic polymorphization for `RawVec` today (always passing the `Layout` as a parameter), but we could try doing a middle-ground by having the `Layout` (or just the `Alignment`) as a const generic parameter to split the difference.
nikomatsakis: Building on scott's question, it seems like we could take some inspiration from the way we do coherence, and say that you can create instances of structs with private/non-exhaustive fields, but only within that privacy zone -- and then I'm having trouble seeing what forwarwds compatibility issue we are creating. It seems like the act of creating an instance of the struct inside a `const fn` kind of "opts-in" to continuing to do that, I'm not clear yet on what also declaring it on the declaration site is buying you in addition.
scottmcm: I think there's a distinction between "can be created with `const fn new`" and "can be passed through a const parameter"? So perhaps the distinction on the declaration would be in letting *other* crates put it in const parameters and being confident that the `Foo::new()` won't add some field incompatible with that later.
nikomatsakis: Ah, *right*, so you can create things in const (not exactly RVs, more like CVs, but I think the doc calls them RVs) for which there is no TSV. So this is an opt-in that says "every RV must also be a TSV". I am curious how much this aligns with (e.g.) structural equality. I feel like I don't yet grok the mental model of how users should think about this whole zoo of distinctions, which worries me.
scottmcm: even simple types like ascii char have private fields. Lots of really useful types have private types.
Boxy: I don't think this is realistic.
Boxy: The difficulty with how we want to handle references, pointers, floats -- we need to resolve those first. ANd we also need to figure out the story of supporting const parameters in const-generic.
Niko: Sounds like you're assuming a particular model of what types and values can appear in private fields that's very permissive. What if we make it more restricted? If all those private fields were turned to public and the entire program compiled, then no one's using them outside of the private module. Then that works. Could you have the exact same rules you'd apply to the fields except that they're private?
Boxy: In terms of lossy conversion the compiler relies on those. If we made reference be TSV compatible in future, then
Niko: But you'd have opted in with the derive.
Tyler: The interaction between private fields and types like references is more complicated than just supporting one or the other.
Niko: Privatne fields have teh extra promise that doesn''t exist in the public fields. It's also a language soundness promise especially around lossiness. And we require you from the point of view of the unsafe code to assume the worst and an a comment doesn't suffice. But you are still putting a derive here.
Niko: You have a struct with derive. Later we allow the ampersand type and you put it there. But when we decided to put an ampersand type and we'd document it, then you adding the ampersand type is your fault.
Boxy: The opt-in could say something like if you derive this you can't have unsafe safety invariants. There's also a related thing of type inference -- maybe you wind up with a value in the type system that wasn't explicitly constructed.
nikomatsakis: If we said:
- you can derive `ConstGenericCompatible` on structs and for that to be legal:
- the types of all fields must be `ConstGenericCompatible`
- and the type must be a struct that is "exhaustive" (not non-exhaustive)
- and the trait is implemented by default only for
- i32, u32, ... scalar types
- char
- tuples of other const-generic-compatible types
if we extend "const generic compatible" to include `&T`, I argue that this is fine, because you'd have to have added the derive to your struct anyway, and you couldn't have been using `&T`, so when you change over to use `&T`, that's the action that is "blamed".
Boxy: I can't think of one and lcnr also couldn't think of one. With type inference it's less about lossy conversion and more about any kind of safety invariant.
Niko: That kind of sounds like a problem. Not specific to const generics.
Niko: You're saying now we're introducing inference variables that have values not just types.
Boxy: We already have that on stable for array lengths.
Niko: The analogous example is unsoundness we got from the never type. There was unsafe code that was was not sound for `T = !` but this never caused issues because safe code had inference variables falling back to `()`; but when we changed them to fallback to `!` that safe code became unsound. I sort of see what you mean.
Tyler: What questtions do we need to answer to be comfortable allowing private fields? Maybe need to document taht if you have this derive you're guaraneteing you won't have safety invariants not just on the current fields but also any future fields.
Niko: You wouldn't be allowed to add those fields for the time being -- it falls out from the rules that say "you can only have fields of this limited set of types".
Tyler: Can we break down a const inference variable if it's an ADT into its component fields? I can imagine doing that with types whose fields are all accessible.
Niko: I'm still struggling with the inference example.
Boxy: Maybe you wind up never writing the expression in the first place? Maybe the compiler would write
Boxy:
```rust
fn foo<N : usize>() -> Bar<
// SAFETY: Some Trait Bound ensures this is ok...
unsafe { Qux { field: N } }
>,
where
(): IsSoundFor<{N}>,
```
Boxy: When you call `foo` you have to prove the trait bounds of the function. The trait bounds might be the argument the unsafe code is sound. Then we have this type system value that shouldn't exist and we'll at some point check we've created in the past. But in the meantime we might do something with the value in the const eval.
Niko: This sounds like a well-formedness example. I'm not quite seeing it yet.
Tyler: This is another example where we have a type with a safety invariant. In your example it's an unsafe field.
Niko: But still the struct has to opt in.
Tyler: The opt-in really feels protective to me.
Niko: We could say "don't do insane things". There does seem to be a pretty big boost for users to also have private fields. I'm not sure what we're saving space for.
TC: What's the difference between this and something with a bound as above but where the return type has an array length determined by a call to an unsafe const function?
Niko:
```rust
fn foo<N : usize>() -> Bar<
// SAFETY: Some Trait Bound ensures this is ok...
unsafe { something_that_returns_qux(N) }
>,
where
(): IsSoundFor<{N}>,
```
Boxy: The fact we've been talking about this for this long is an indication that private fields and safety invariants are nontrivial?
Tyler: I feel we're conflating private fields and safety invariants.
Niko: I feel we've not seen one example where the interaction that wouldn't be addressed by the limitation of what types you can put in there. "There's a set of types you could write with the derive today but that would be invalid with a future extension".
Boxy: We could support private fields and hope they won't cause issues in the future.
Boxy: The initial proposal was forwards-compatible with private fields.
Niko: I'm not against the original proposal but I'd still love to read something to help users understand what they'd with the derive.
Boxy: The initial proposal is about non_exhaustive enums which isn't really compatible with the struct case.
Niko: If we keep round-tripping, I don't know what the argument is.
Boxy: If we don't have round-tripping, the only arguments we have is semver stuff.
Niko: The other compelling reason you left out is private fields in structs. You're committing to not add fields that don't have this property to be const-generic compatible. Once you extend that it to privacy, the opt-in makes sense.
Boxy: Yes.
Niko: Okay, that's pretty compelling.
Niko: I'm of the opinion that losing round-tripping is a big problem. But losing some equality is less concerning. I think the story of double equals calls this impl, pattern matching is a structural thing and equality in these context is as if it was expanded into this pattern.
TC: The equality question seems connected to the question of round tripping. If we're willing to not have that, it opens the door to doing normalizations, and those normalizations might allow us to achieve a more normal notion of equality. Aside from the question `-0.0` / `+0.0`, there's also the question of what to do about struct padding.
Niko: To me, floats and minus/positive zero (or nan boxing) feels different than padding. minus/plus zero is *weird* but distinctly observable, and I don't think it's "UB" to observe it, but messing with padding is like clearly nuts (and I think UB in the C spec? at least some of the time?)
## What kinds of things can we pass in?
scottmcm: As I was typing the above, I realized that using these types would also need at least being able to pass `foo::<{size_of::<T>()}>(p)` or similar. Would allowing things like that also be part of the first pass? (If I have to hide it via an associated const on a trait that would be fine too.)