Why is T: 'static required for soundness in TypeID (and thus Any)

Hi there,

I have recently found myself writing code where I have some trait Object dyn Trait
where

trait Trait<'a> where
Self: Any
{
// ...
}

Then I use that trait to use it as a Trait object in another struct

struct OtherS<'a>(&'a mut dyn Trait<'a>);

and wanted to implement PartialEq for OtherS as I wanted to create a Data structure which is unique over the concrete type. I then noticed that Any requires T: 'static and I understand that it is for soundness reasons as one could downcast some T<'a> to T<'static> if it would not, as TypeId's obviously don't contain any lifetime Information.

My question now is why not just implement downcast_ref like so

impl dyn Any + 'static {
    fn downcast_ref<T>(...) -> ...
}

instead of like it is currently (as follows):

impl dyn Any {
    fn downcast_ref<T>(...) -> ...
}

this should still ensure soundness as downcast_ref can only be used where T:'static, and should allow other use cases like mine mentioned above, no?

Thanks for any clarification
and Best Wishes

Justus Flügel

2 Likes

I think the question is not "why do we need 'static here at all?", but "why do we restrict the trait itself and not the methods?" And this actually deserves attention, since, well, it's generally advised to do otherwise - restrict everything only at the very place where the restriction is necessary (e.g. HashMap doesn't require keys to be Hash - only the HashMap::insert et al. do).

2 Likes

Could you elaborate on your use-case? I'm having a hard time understanding it. What would the implementation of PartialEq look like?

Note that while the design of moving the bound to the relevant downcast methods may be sound, for the existing Any trait it's a breaking change since right now a T: Any bound is sufficient to do downcasting in a generic function.

3 Likes

Also, existing code may be relying on the fact that Any implies 'static to, for example, capture an Any + Send type in a thread::spawn closure.

1 Like

It makes sense: Rust Playground

trait Any : 'static {
    fn new() -> Self;
}
// implied bound
fn f<T: Any + Send>(){
    std::thread::spawn(|| T::new());
}

trait Any_ {
    fn new() -> Self;
}

// error[E0310]: the parameter type `T` may not live long enough
// help: consider adding an explicit lifetime bound...
fn g<T: Any_ + Send /* + 'static */>(){
    std::thread::spawn(|| T::new());
}

2089-implied-bounds - The Rust RFC Book

1 Like

Well, for one, it may technically be sound, but it would still allow logical bugs by equating the TypeIds of two types with different lifetimes.

1 Like

It also changes the meaning of most &dyn Any and &'a dyn Any annotations.

Edit: Oh yeah, and removes an implicit 'dyn_lifetime: 'static bound.

4 Likes

Yep that is exactly my question.

Yep, sure, here you go:

I have some Trait and struct as stated above

trait Trait<'a> where
Self: Any
{
// ...
}

struct OtherS<'a>(&'a mut dyn Trait<'a>);

and would then like to something like the following

impl<'a> std::cmp::PartialOrd for OtherS<'a> {
    fn partial_cmp(&self, other: &Rhs) -> Option<Ordering> {
       Some(std::cmp::Ord(self,other))
    }
}

impl<'a> PartialEq for OtherS<'a> {
    fn eq(&self, other: &Self) -> bool {
         (*self.0).type_id() == (*other.0).type_id()
    }
}

impl std::cmp::Ord for OtherS<'a> {
    fn cmp(&self, other: &Rhs) -> Ordering {
        std::cmp::Ord::cmp((*self.0).type_id() as usize, (*other.0).type_id() as usize)
    }
}

impl std::cmp::Eq for OtherS<'a> {}

and use it in a BTreeSet to have a set of "unique" types like so

#[test]
fn some_test() {
    let set: BTreeSet<OtherS> = BTreeSet::new();
}

Or alternatively something like

#[test]
fn some_test() {
    let set: BTreeMap<TypeId, OtherS> = BTreeMap::new();
}

which I can then encapsulate in my own struct for better ergonomics.

I then use this Type inside one of my proc macros where i have a kind of Generator Trait which should generate code once per Struct that implements it (So that's what this question is about), once per instance (which I then call a Template) and once per usage of that template in a method on this trait, which is also easy enough. I am just kinda stuck on how to ensure that every "level" is only called once (and additionally every level may return data to which a reference is passed to each of the following levels)

Here is my actual trait if that helps

pub trait Generatable {
    type GeneratableData;
    type TemplateData;
    type Assert;

    fn generatable(context: &mut Context) -> PassedData<Self::GeneratableData>
    where
        Self: Sized;

    fn template(
        &self,
        context: &mut Context,
        passed: &Self::GeneratableData,
    ) -> PassedData<Self::TemplateData>;

    fn assert(
        &self,
        _context: &mut Context,
        _passed: (&Self::GeneratableData, &Self::TemplateData),
        _to_assert: &Self::Assert,
    ) -> Option<TokenStream> {
        None
    }
}

The end goal is to have kind of "asserts" for proc _macros similar to static_assertions - Rust which generate code that asserts inputs to be of a specific type (see the static_assertions docs for more details).

Some specific Generatable would then be used like so ( where TraitTemplate implements Generatable)

let r#type = todo!(); /* some syn::Type from the macro input */

let assertion_store = assertions::Store::new();

assertion_store.assert(TraitTemplate::new(syn::parse_quote!(::std::fmt::Debug)).test(r#type));

quote!{
#store
}

(test and so on will come from a Template Trait which is implemented for any T: Generatable)

maybe I will also add a macro to simplify it a bit further, something like

assert_into!(store | r#type impl ::std::fmt::Debug);

Here the actual impl for Generatable for TraitTemplate that already exists (which checks for a type to implement some trait)

pub struct Trait<'a> {
    trait_bound: &'a TraitBound,
}
impl<'a> Generatable for Trait<'a> {
    type GeneratableData = ();
    type TemplateData = Ident;
    type Assert = syn::Type;

    fn template(
        &self,
        Context {
            ident_generator, ..
        }: &mut Context,
        _passed: &Self::GeneratableData,
    ) -> PassedData<Self::TemplateData> {
        let fn_ident = ident_generator.prefixed("assert_trait_bound");
        let trait_bound = self.trait_bound;

        quote! {
            fn #fn_ident<T: #trait_bound>() {}
        }
        .with_data(fn_ident)
    }

    fn assert(
        &self,
        _context: &mut Context,
        (_, assert_trait_bound): (&Self::GeneratableData, &Self::TemplateData),
        to_assert: &Self::Assert,
    ) -> Option<TokenStream> {
        Some(quote! {
            #assert_trait_bound::<#to_assert>();
        })
    }

    fn generatable(_context: &mut Context) -> PassedData<Self::GeneratableData>
    where
        Self: Sized,
    {
        PassedData::default()
    }
}

or here one for type equality

pub struct Type<'a> {
    pub r#type: &'a Ident,
    pub generics_count: usize,
}

impl<'a> Generatable for Type<'a> {
    type GeneratableData = Ident;
    type TemplateData = ();
    type Assert = syn::Type;

    fn generatable(
        Context {
            ident_generator, ..
        }: &mut Context,
    ) -> PassedData<Self::GeneratableData> {
        let ident = ident_generator.prefixed("assert_type_eq");

        quote! {
            trait TypeEq {
                type This: ?Sized;
            }
            impl<T: ?Sized> TypeEq for T {
                type This = Self;
            }

            fn #ident<T, U>()
            where
                T: ?Sized + TypeEq<This = U>,
                U: ?Sized,
            {
            }
        }
        .with_data(ident)
    }

    fn assert(
        &self,
        _context: &mut Context,
        (assert_type_eq, _): (&Self::GeneratableData, &Self::TemplateData),
        to_assert: &Self::Assert,
    ) -> Option<TokenStream> {
        let type_bound = self.r#type;

        if self.generics_count > 0 {
            let generic_ident = Ident::new("_", Span::call_site());
            let generic_ident_iter = iter::repeat(generic_ident).take(self.generics_count);

            Some(quote! {
                #assert_type_eq::<#to_assert,#type_bound<#(#generic_ident_iter),*>>();
            })
        } else {
            Some(quote! {
                #assert_type_eq::<#to_assert,#type_bound>();
            })
        }
    }

    fn template(
        &self,
        _context: &mut Context,
        _passed: &Self::GeneratableData,
    ) -> PassedData<Self::TemplateData>
    where
        Self::TemplateData: Default,
    {
        PassedData::default()
    }
}

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.