- Notifications
You must be signed in to change notification settings - Fork13.6k
Stabilize const TypeId::of#144133
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:master
Are you sure you want to change the base?
Stabilize const TypeId::of#144133
Conversation
Some changes occurred to MIR optimizations cc @rust-lang/wg-mir-opt |
This comment has been minimized.
This comment has been minimized.
d3b3f08
tod94f60f
Compare This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as resolved.
type_name
d94f60f
to55cc0ba
CompareRalfJung commentedJul 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
OTOH type_name is so clearly documented as being not injective that I am totally fine with stabilizing it if there's a good usecase. Every API relying on type_name comparison is so obviously unsound, I'm not worried about this happening accidentally. But there's no reason to stabilize it together with TypeId::of. |
The job Click to see the possible cause of the failure (guessed by this bot)
|
theemathas commentedJul 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I'm not sure what exactly you mean by this, but here's some weird code. #![feature(const_type_id)]use std::mem::transmute;use std::any::TypeId;constfnassert_same_type<T:'static,U:'static>(){unsafe{let id1:TypeId =TypeId::of::<T>();let[ptr1, _]:[*constu8;2] =transmute(id1);let id2:TypeId =TypeId::of::<U>();let[ptr2, _]:[*constu8;2] =transmute(id2);let _ = ptr1.offset_from(ptr2);// This also works:// let _ = ptr1.wrapping_add(1).offset_from(ptr2.wrapping_add(1)));}}constWORKS:() =assert_same_type::<i32,i32>();constFAILS:() =assert_same_type::<i32,i64>(); Compile error
|
RalfJung commentedJul 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yes it's possible to write a function that is UB (and thus may halt evaluation) if and only if the two TypeId are different. I wouldn't call this a valid equality test... You can't use this to do anything conditional on that result though, it just stops compilation (or does whatever else const UB may do). So not sure if it's worth trying to stop this. |
LMAO I guess we should have gone with Ralf's route of not reusing |
theemathas commentedJul 18, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
I managed to write some code that compiles or not depending on the hash value inside Code(tested on the playground with #![feature(const_type_id)]use std::mem::transmute;use std::any::TypeId;/*// I used this code to make the compiler tell me what the hash istrait Super {}trait Sub: Super {}const SHOW_HASH_1: () = unsafe { let id = TypeId::of::<i32>(); let [first, second]: [*const (); 2] = transmute(id); let ptr: *const dyn Sub = transmute([second, first]); let _: *const dyn Super = ptr;};const SHOW_HASH_2: () = unsafe { let id = TypeId::of::<i32>(); let ptr: *const dyn Sub = transmute(id); let _: *const dyn Super = ptr;};*/constHASH_1:usize =0x50bb9674fa2df013;constHASH_2:usize =0x56ced5e4a15bd890;constfnassert_is_i32<T:'static,constA:usize,constB:usize>(){const{unsafe{let id =TypeId::of::<T>();let[ptr1, _]:[*constu8;2] =transmute(id);&*ptr1.wrapping_sub(A).cast::<()>()}};const{unsafe{let id =TypeId::of::<T>();let[_, ptr2]:[*constu8;2] =transmute(id);&*ptr2.wrapping_sub(B).cast::<()>()}};}constWORKS:() =assert_is_i32::<i32,HASH_1,HASH_2>();constFAILS:() =assert_is_i32::<i64,HASH_1,HASH_2>();
|
I think that is the same situation as you had before. You can't make an |
This comment was marked as resolved.
This comment was marked as resolved.
RalfJung commentedJul 18, 2025 via email• edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Yeah, that's fundamentally the same thing as above. |
I think I agree that I'm not particularly concerned by code like that. Compared to other ways that you can break compilation between versions using const, it feels very minor. For example, you cancurrently compile u128::from_be_bytes([0; core::char::UNICODE_VERSION.0as_]) which is obviously a bad idea, so I think any transmute tricks on TypeId aren't worth worrying about so long as they don't let you bust the use-the-integer-value firewall. |
In combination with Code#![feature(const_type_id, const_raw_ptr_comparison)]use std::any::TypeId;use std::mem::transmute;use std::ptr::null;constHASH_1:usize =0x50bb9674fa2df013;constHASH_2:usize =0x56ced5e4a15bd890;constfnis_i32<T:'static>() ->bool{unsafe{let id =TypeId::of::<T>();let[ptr1, ptr2]:[*constu8;2] =transmute(id);matches!(ptr1.wrapping_sub(HASH_1).guaranteed_eq(null()),Some(false)) &&matches!(ptr2.wrapping_sub(HASH_2).guaranteed_eq(null()),Some(false))}}constYES:bool =is_i32::<i32>();constNAH:bool =is_i32::<i64>();fnmain(){println!("{YES}");// prints "true"println!("{NAH}");// prints "false"} |
A possible solution is to make CTFE pointer comparison check the provenance and always return unknown if either or both pointers are |
Raw pointer comparison in consts are unstable and will stabilize after const traits if ever. With const traits we get real comparisons and do not need to concern ourselves with preventing hacky workarounds |
Uh oh!
There was an error while loading.Please reload this page.
fixes#77125
Stabilization report for
const_type_id
General design
What is the RFC for this feature and what changes have occurred to the user-facing design since the RFC was finalized?
N/A the constness was never RFCed
What behavior are we committing to that has been controversial? Summarize the major arguments pro/con.
const_type_id
was kept unstable because we are currently unable to stabilize thePartialEq
impl for it (in const contexts), so we feared people would transmute the type id to an integer and compare that integer.Are there extensions to this feature that remain unstable? How do we know that we are not accidentally committing to those?
TypeId::eq
is not const at this time, and will only become const once const traits are stable.Has a Call for Testing period been conducted? If so, what feedback was received?
This feature has been unstable for a long time, and most people just worked around it on stable by storing a pointer to
TypeId::of
and calling that at "runtime" (usually LLVM devirtualized the function pointer and inlined the call so there was no real performance difference).A lot of people seem to be using the
const_type_id
feature gate (600 results for the feature gate on github:https://github.com/search?q=%22%23%21%5Bfeature%28const_type_id%29%5D%22&type=code)We have had very little feedback except desire for stabilization being expressed.
Implementation quality
Until these three PRs
there was no difference between the const eval feature and the runtime feature except that we prevented you from using
TypeId::of
at compile-time. These three recent PRs have hardened the internals ofTypeId
:TypeId
for an equality check. This also guards against creating values of typeTypeId
by any means other thanTypeId::of
Summarize the major parts of the implementation and provide links into the code (or to PRs)
N/A see above
Summarize existing test coverage of this feature
Since we are not stabilizing any operations on
TypeId
except for creatingTypeId
s, the test coverage of the runtime implementation ofTypeId
covers all the interesting use cases not in the list belowHardening against transmutes
TypeId::eq is still unstable
What outstanding bugs in the issue tracker involve this feature? Are they stabilization-blocking?
#129014 is still unresolved, but it affects more the runtime version of
TypeId
than the compile-time.What FIXMEs are still in the code for that feature and why is it ok to leave them there?
none
Summarize contributors to the feature by name for recognition and assuredness that people involved in the feature agree with stabilization
Which tools need to be adjusted to support this feature. Has this work been done?
N/A
Type system and execution rules
What compilation-time checks are done that are needed to prevent undefined behavior?
Already covered above. Transmuting types with private fields to expose those fields has always been library UB, but for the specific case of
TypeId
CTFE and Miri will detect it if that is done in any way other than for reconstructing the exact sameTypeId
in another location.Does the feature's implementation need checks to prevent UB or is it sound by default and needs opt in in places to perform the dangerous/unsafe operations? If it is not sound by default, what is the rationale?
N/A
Can users use this feature to introduce undefined behavior, or use this feature to break the abstraction of Rust and expose the underlying assembly-level implementation? (Describe.)
N/A
What updates are needed to the reference/specification? (link to PRs when they exist)
Nothing more than what needs to exist for
TypeId
already.Common interactions
Does this feature introduce new expressions and can they produce temporaries? What are the lifetimes of those temporaries?
N/A
What other unstable features may be exposed by this feature?
N/A