Skip to content

Stabilize const_type_id feature#72488

Merged
bors merged 4 commits intorust-lang:masterfrom
KodrAus:stabilize/const_type_id
Jul 29, 2020
Merged

Stabilize const_type_id feature#72488
bors merged 4 commits intorust-lang:masterfrom
KodrAus:stabilize/const_type_id

Conversation

@KodrAus
Copy link
Contributor

@KodrAus KodrAus commented May 23, 2020

The tracking issue for const_type_id points to the ill-fated #41875. So I'm re-energizing TypeId shenanigans by opening this one up to see if there's anything blocking us from stabilizing the constification of type ids.

Will wait for CI before pinging teams/groups.


This PR stabilizes the const_type_id feature, which allows TypeId::of (and the underlying unstable intrinsic) to be called in constant contexts.

There are some sanity tests that demonstrate its usage, but I’ve included some more below.

As a simple example, you could create a constant item that contains some type ids:

use std::any::TypeId;

const TYPE_IDS: [TypeId; 2] = [
    TypeId::of::<u32>(),
    TypeId::of::<i32>(),
];

assert_eq!(TypeId::of::<u32>(), TYPE_IDS[0]);

Type ids can also now appear in associated constants. You could create a trait that associates each type with its constant type id:

trait Any where Self: 'static {
    const TYPE_ID: TypeId = TypeId::of::<Self>();
}

impl<T: 'static> Any for T { }

assert_eq!(TypeId::of::<usize>(), usize::TYPE_ID);

TypeId::of is generic, which we saw above in the way the generic Self argument was used. This has some implications for const evaluation. It means we can make trait impls evaluate differently depending on information that wasn't directly passed through the trait system. This violates the parametricity property, which requires all instances of a generic function to behave the same way with respect to its generic parameters. That's not unique to TypeId::of, other generic const functions based on compiler intrinsics like mem::align_of can also violate parametricity. In practice Rust doesn't really have type parametricity anyway since it monomorphizes generics into concrete functions, so violating it using type ids isn’t new.

As an example of how impls can behave differently, you could combine constant type ids with the const_if_match feature to dispatch calls based on the type id of the generic Self, rather than based on information about Self that was threaded through trait bounds. It's like a rough-and-ready form of specialization:

#![feature(const_if_match)]

trait Specialized where Self: 'static {
    // An associated constant that determines the function to call
    // at compile-time based on `TypeId::of::<Self>`.
    const CALL: fn(&Self) = {
        const USIZE: TypeId = TypeId::of::<usize>();

        match TypeId::of::<Self>() {
            // Use a closure for `usize` that transmutes the generic `Self` to
            // a concrete `usize` and dispatches to `Self::usize`.
            USIZE => |x| Self::usize(unsafe { &*(x as *const Self as *const usize) }),
            // For other types, dispatch to the generic `Self::default`.
            _ => Self::default,
        }
    };

    fn call(&self) {
        // Call the function we determined at compile-time
        (Self::CALL)(self)
    }
    
    fn default(x: &Self);
    fn usize(x: &usize);
}

// Implement our `Specialized` trait for any `Debug` type.
impl<T: fmt::Debug + 'static> Specialized for T {
    fn default(x: &Self) {
        println!("default: {:?}", x);
    }
    
    fn usize(x: &usize) {
        println!("usize: {:?}", x);
    }
}

// Will print "usize: 42"
Specialized::call(&42usize);

// Will print "default: ()"
Specialized::call(&());

Type ids have some edges that this stabilization exposes to more contexts. It's possible for type ids to collide (but this is a bug). Since they can change between compiler versions, it's never valid to cast a type id to its underlying value.

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. merged-by-bors This PR was explicitly merged by bors. relnotes Marks issues that should be documented in the release notes of the next release. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-lang Relevant to the language team T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.