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

ACP: add std::any::is_same_type::<T, U>() -> bool #411

Closed
tgross35 opened this issue Jul 12, 2024 · 7 comments
Closed

ACP: add std::any::is_same_type::<T, U>() -> bool #411

tgross35 opened this issue Jul 12, 2024 · 7 comments
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api

Comments

@tgross35
Copy link

Proposal

Problem statement

#231 was recently accepted as a way to check if two types are the same in const contexts, providing a new TypeId::matches. However, this API suffers from a few problems:

  1. Its goal is to be useful at compile time, but it relies on being able to construct TypeIds. Whether or not we can provide a safe const TypeId::of is not well known at this time; if it is possible, it will likely be a while off. (Tracking Issue for const fn type_id rust#77125, Collisions in type_id rust#10389)
  2. If we eventually get a const PartialEq, TypeId::matches will become a duplicate of a more natural API.

These two problems seem to provide significant barriers to eventual stabiliztion of TypeId::matches

Motivating examples or use cases

Most of the use cases for const type equality checks is comparing types that are available either as a generic or as a concrete type, rather than unknown TypeIds. This is currently true by default since const TypeId::of is not available, but seems likely to be the case more generally.

One use case of checking types for equality is to provide downcasting behavior:

use core::ptr;

const fn const_downcast<T, Expected>(value: &T) -> Option<&Expected> {
    if /* magic expression */ {
        Some( unsafe { &*ptr::from_ref(value).cast::<Expected>() })
    } else {
        None
    }
}

#231 provides a more complete example.

Solution sketch

Rather than provide an interface that relies on TypeIds, provide a standalone function that takes the types as generic parameters:

// core::any

/// Return `true` if `T` and `U` are the same type.
const fn is_same_type<T: 'static, U: 'static>() -> bool {
    // or intrinsic call
    TypeId::of::<T>().t == TypeId::of::<U>().t
}

Usage:

use core::{any, ptr};

const fn const_downcast<T, Expected>(value: &T) -> Option<&Expected> {
    if any::is_same_type::<T, Expected>() {
        Some( unsafe { &*ptr::from_ref(value).cast::<Expected>() })
    } else {
        None
    }
}

This can be implemented using TypeId (as in this example), but it could also be implemented as an intrinsic. The intrinsic option allows hooking into compiler logic to provide the check, meaning const stability is not blocked on const TypeId uncertainties.

Usage is also simpler, which means it serves a simplification purpose even after const PartialEq.

Alternatives

Links and related work

What happens now?

This issue contains an API change proposal (or ACP) and is part of the libs-api team feature lifecycle. Once this issue is filed, the libs-api team will review open proposals as capability becomes available. Current response times do not have a clear estimate, but may be up to several months.

Possible responses

The libs team may respond in various different ways. First, the team will consider the problem (this doesn't require any concrete solution or alternatives to have been proposed):

  • We think this problem seems worth solving, and the standard library might be the right place to solve it.
  • We think that this probably doesn't belong in the standard library.

Second, if there's a concrete solution:

  • We think this specific solution looks roughly right, approved, you or someone else should implement this. (Further review will still happen on the subsequent implementation PR.)
  • We're not sure this is the right solution, and the alternatives or other materials don't give us enough information to be sure about that. Here are some questions we have that aren't answered, or rough ideas about alternatives we'd want to see discussed.
@tgross35 tgross35 added api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api labels Jul 12, 2024
@tgross35
Copy link
Author

tgross35 commented Jul 12, 2024

This is really mostly a counterproposal to #231, looking to address some of its limitations if this is to be a library feature. I do think that a lang feature allowing T == U should probably be considered because it is more intuitive, and could be extended to provide type narrowing without const_downcast. I think this has been discussed before, possibly in the context of const if.

Cc @KodrAus since most TypeId things are your design.

@RalfJung
Copy link
Member

ISTM most of the time one of the types involved is dynamic, a la &dyn Any, and only the other type is statically known (like in the existing downcast methods)? How often are both types statically known?

@programmerjake
Copy link
Member

programmerjake commented Jul 14, 2024

I have encountered both types being statically known a few times e.g.

pub enum Select<T, F> {
    True(T),
    False(F),
}

pub trait Dispatch {
    type Type<const V: bool>: 'static;
}

pub fn select<T: Dispatch, const V: bool>(v: &mut T::Type<V>) -> Select<&mut T::Type<true>, &mut T::Type<false>> {
    if V {
        Select::True(<dyn Any>::downcast_mut(v).unwrap())
    } else {
        Select::False(<dyn Any>::downcast_mut(v).unwrap())
    }
}

@tgross35
Copy link
Author

ISTM most of the time one of the types involved is dynamic, a la &dyn Any, and only the other type is statically known (like in the existing downcast methods)? How often are both types statically known?

I haven't seen any examples that seem to show this is the case in const functions. The example in #231 and the UI test removed from rust-lang/rust#99189 both use one generic and one specified type. Are there examples of this that couldn't be made to work by propagating a generic type, rather than passing a TypdId?

@NobodyXu
Copy link

There's a crate castaway which has more generic API without having to specify the 'static bound.

It uses a LifetimeFree trait to constraint the type to be cast, to avoid lifetime issues.

@RalfJung
Copy link
Member

Are there examples of this that couldn't be made to work by propagating a generic type, rather than passing a TypdId?

The typical example I was thinking of is code that handles dyn Any types, and then downcasts them back to their original type.

@Amanieu
Copy link
Member

Amanieu commented Jul 16, 2024

We discussed this in the libs-api meeting. We don't think this method will be very useful in practice because you often need to deal with a dynamic TypeId.

One alternative that we discussed was a TypeId::is method similar to the existing <dyn Any>::is which checks if a TypeId refers to the given type.

@Amanieu Amanieu closed this as completed Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-change-proposal A proposal to add or alter unstable APIs in the standard libraries T-libs-api
Projects
None yet
Development

No branches or pull requests

5 participants