Skip to content
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

Comprison of TypeIds in const context #73900

Open
WaffleLapkin opened this issue Jun 30, 2020 · 9 comments
Open

Comprison of TypeIds in const context #73900

WaffleLapkin opened this issue Jun 30, 2020 · 9 comments
Labels
A-const-fn Area: const fn foo(..) {..}. Pure functions which can be applied at compile time. S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work.

Comments

@WaffleLapkin
Copy link
Member

Is there any way to compare 2 TypeIds in const context? (It's possible to create them in const context, and it's even soon to be stabilized)

What I want is a static check for type (un)equality without auto traits:

// will fail with index out of bounds if T == U
const _: () = [()][(TypeId::of::<T>() == TypeId::of::<U>()) as usize];

(this exact code currently fails to compile with "calls in constants are limited to constant functions, tuple structs and tuple variants" error, because == isn't const)

Maybe it's a good idea to add API like the following to TypeId?

impl TypeId {
    const fn eq(&self, rhs: &Self) -> bool {
        self.t == rhs.t
    }
}

I could work on this, though I haven't seen similar apps in std, so I wanted to ask about it before doing anything.

@KodrAus
Copy link
Contributor

KodrAus commented Jul 1, 2020

There's a snippet in the stabilization PR that compares two type ids, but it does it using match:

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,
}

@WaffleLapkin
Copy link
Member Author

Oh, I've missed it 🤦 Then I think this issue is not critical at all. Thanks.

However match won't work with generics, you can't write code like this:

const fn type_eq<A: 'static, B: 'static>() -> bool {
    let A = TypeId::of::<A>();
    const B: TypeId = TypeId::of::<B>(); // error[E0401]: can't use generic parameters from outer function
    match A {
        B => true,
        _ => false,
    }
}

(playground)

@nbdd0121
Copy link
Contributor

nbdd0121 commented Jul 1, 2020

I think this is blocked by #67792, which would allow PartialEq implementation of TypeId to be marked as const.

@rustbot modify labels: A-const-fn S-blocked

@rustbot rustbot added A-const-fn Area: const fn foo(..) {..}. Pure functions which can be applied at compile time. S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. labels Jul 1, 2020
@WaffleLapkin
Copy link
Member Author

We could add stand-alone function and then replace it by const imply. (if we'll do this before the stabilization of stand-alone fns, it won't be even a breaking change)

@rodrimati1992
Copy link
Contributor

rodrimati1992 commented Jul 2, 2020

You can use associated constants to get the TypeId of a generic type:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=50e369ee80dcbceab6563d183cf8c894

#![feature(const_type_id)]

use std::any::{type_name, TypeId};

pub struct GetTypeId<T>(T);

impl<T: 'static> GetTypeId<T> {
    pub const VALUE: TypeId = TypeId::of::<T>();
}

#[macro_export]
macro_rules! typeid {
    ($t:ty) => {
        $crate::GetTypeId::<$t>::VALUE
    };
}

const fn same_type<T: 'static, U: 'static>() -> bool {
    match typeid!(T) {
        typeid!(U) => true,
        _ => false,
    }
}

fn print_if_equal<T: 'static, U: 'static>() {
    if same_type::<T, U>() {
        println!("{} == {}", type_name::<T>(), type_name::<U>());
    } else {
        println!("{} != {}", type_name::<T>(), type_name::<U>());
    }
}

fn main() {
    print_if_equal::<usize, u32>();
    print_if_equal::<usize, usize>();
}

Edit:
I just noticed that the match doesn't work, it prints this:

usize != u32
usize != usize

Reported this in #73976

@WaffleLapkin
Copy link
Member Author

Then I'll go ahead and close this issue, technically comparison can be done. The only problem is a bug reported in #73976

I've actually used similar hack with assoc const, how I could forget... anyway, thanks @rodrimati1992

@nbdd0121
Copy link
Contributor

@WaffleLapkin The pattern in #73976 is now forbidden. However you can use this transmute hack:

#![feature(const_fn)]
#![feature(const_type_id)]

use std::any::TypeId;
use std::any::type_name;
use std::mem::transmute;

const fn same_type<A: 'static, B: 'static>() -> bool {
    unsafe { transmute::<_, u64>(TypeId::of::<A>()) == transmute::<_, u64>(TypeId::of::<B>()) }
}

fn print_if_equal<T: 'static, U: 'static>() {
    if same_type::<T, U>() {
        println!("{} == {}", type_name::<T>(), type_name::<U>());
    } else {
        println!("{} != {}", type_name::<T>(), type_name::<U>());
    }
}

fn main() {
    print_if_equal::<usize, u32>();
    print_if_equal::<usize, usize>();
}

@WaffleLapkin
Copy link
Member Author

@nbdd0121 it's actually a de jure UB since TpeId has neither #[repr(C)] nor #[repr(transparent)], so I don't think it's an "ok" solution.

(reopening the issue since the pattern from #73900 (comment) is now forbidden)

@WaffleLapkin WaffleLapkin reopened this Jul 27, 2020
@raldone01
Copy link
Contributor

See this pr for more information #101698.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-fn Area: const fn foo(..) {..}. Pure functions which can be applied at compile time. S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work.
Projects
None yet
Development

No branches or pull requests

6 participants