-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Associated const underscore #3527
base: master
Are you sure you want to change the base?
Conversation
73c9d1f
to
9db99f3
Compare
When does associated const code get run? at type definition? when substituting concrete types into generic arguments? never? pub struct S<T>(T);
impl<T> S<T> {
const _: () = assert!(core::mem::size_of::<T>() % 2 == 0);
} when does the |
Great question! I had not thought about that. I have added a paragraph proposing that they are evaluated never, aligning with the behavior of a named associated constant that is accessed never, but I'd welcome considering other options. |
9db99f3
to
5055393
Compare
I think that never being evaluated will be highly unintuitive, we should instead require them to be evaluated (though memoization is allowed) when generating any code that creates a type so: pub struct S<T, U, const V: usize>(T, U);
impl<T, U, const V: usize> S<T, U, V> {
const _: () = {
let v: Option<T> = None; // use `T`
panic!("1: T");
};
const _: () = {
let v: Option<(T, U)> = None; // use `T` and `U`
panic!("2: T and U");
};
const _: () = {
let v: Option<(T, U)> = None; // use `T` and `U`
let v = [(); V]; // use `V`
panic!("3: T, U, and V");
};
}
pub fn f<T, U>(v: S<T, U, 8>>) {} // doesn't error, since only V has a concrete value
pub fn g<U, const V: usize>(v: S<i8, U, V>>) {} // errors with message "1: T"
pub fn h<const V: usize>() {
let f = f::<u8, i8, V>; // errors with messages "1: T" and "2: T and U"
}
const _: () = {
let v: S<u8, i8, 5>; // errors with messages "1: T" and "2: T and U" and "3: T, U, and V"
}; |
so, basically as if: pub struct S<T, U, const V: usize>(T, U);
impl<T, U, const V: usize> S<T, U, V> {
const _: () = ...;
} was instead written: pub struct S<T, U, const V: usize>(T, U, PhantomData<[(); {Self::_CONST; 0}]>);
impl<T, U, const V: usize> S<T, U, V> {
const _CONST: () = ...;
} |
This would be so helpful! Thanks for writing this, David! In favor of these constants being evaluated: rust-lang/rust#112090 |
It seems that the intent of the change is to allow undocumented static assertions within traits, where this is easier to implement. There are probably corner cases that I cannot think of immediately. I found the use of anonymous const blocks non-intuitive on first encounter but clearly appreciate their use now. I would expect code in const blocks to only affect compilation; therefore, they are fundamentally different than creating symmetry to let bindings. I also anticipate a natural progression that if all of the const blocks symmetry is added, they will be sugared out by keyword or macro. Like C++ static_assert or Zig comptime where intent is clear/concise. |
52c6f4b
to
2495bd6
Compare
I was all set to propose merge as "of course" until I saw the note about evaluation :/ I do agree that not evaluating it is consistent with named associated constants. Unfortunately, I feel like it would be particularly likely for someone to move a Brainstorming: How much would checks here be needed to put into inherent impls vs wanting them only in trait impl blocks? For example, I'm pondering an alternative where you'd expand impl<T> ::core::cmp::Eq for Thing<'a, T> {
#[coverage(off)]
priv fn _assert_fields_are_total_eq<'a, T: ::core::cmp::Eq>() {
let _: ::core::cmp::AssertParamIsEq<Field<'a, T>>;
}
} even though there's no Being a function means the fact that it's not ever evaluated seem more normal, and helper functions in impls that don't have to be put in separate inherent impl blocks seems like an independently-useful feature too. (But also a bigger one, because anything that deals in visibility is complicated.) |
One further thought: the whole |
@rustbot labels -I-lang-nominated We discussed this in the T-lang meeting last week. There was consensus that the problem this is trying to solve is a real problem. However, people were concerned about whether it might be too surprising that these were not evaluated. It wasn't that people wanted them evaluated exactly; the concern was simply the potential for surprise. Aside from the practical motivation (which may be better served by some more specialized feature), the main reason to allow this (with the proposed behavior) would seem to be consistency on two fronts. We allow The consensus was that we wanted more discussion in the RFC itself about this behavior, e.g. acknowledgment that this might be surprising, discussion of things we could do to mitigate the surprise, discussion of why maybe this would be less surprising than it seems or why the consequences of that might not be severe, an argument for why we should do it anyway, etc. Please renominate for T-lang after addressing these in the RFC so the team can review again. |
# Reference-level explanation | ||
[reference-level-explanation]: #reference-level-explanation |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider discussing the relationship with #3373 — probably just to confirm that associated const underscore is not exempt from that lint.
pub struct Struct;
const _: () = {
impl Trait1 for Struct {} // no warning
};
impl Struct {
const _: () = {
impl Trait2 for Struct {} // WARNING
};
}
If the concern is about people being surprised by code not being evaluated, how about introducing wildcard associated functions? Eg: impl Thing {
fn _() {
/* ... */
};
} This would probably be more intuitive to most people. If someone reads this code: impl Thing {
fn _() {
assert!(...);
};
} It's immediately obvious that something is wrong here because that function is obviously never going to be run. |
Finding myself wanting this specifically for assertions: is there a particular reason why we couldn't change the associated type behaviour at an edition boundary, and then warn for associated-const-underscore usages on previous editions? Or is this not specific to associated constants and actually specific for all named constants, and it's just that we're wanting this in particular for the associated constants? Right now I'm using them as a way of confirming validity of generic consts, since even simple things like casting a |
Allow
_
for the name of associated constants. This RFC builds on RFC 2526 which added support for freeconst
items with the name_
, but not associated consts.Constants named
_
are not nameable by other code and do not appear in documentation, but are useful when macro-generated code must typecheck some expression in the context of a specific choice ofSelf
.Rendered