Evaluate macro after blanket impl is expand

I thought it would be interesting to try to re-implement TypeId without the 'static limitation. (I'm writing a presentation on using Rust's dynamic typing capabilities, or lack thereof.)

I know my approach is flawed and will never be useful for any real scenarios. Please just play along.

//! Library source

// Any trait without 'static constraint
pub trait FlexibleAny {
    const TYPE_ID: u64;
}

// I want to generate this impl for every T
impl<T: ?Sized> FlexibleAny for T {
    // Before this new_typeid!() is expanded
    const TYPE_ID: u64 = new_typeid!();
}
//! Proc macro

use std::sync::atomic::{AtomicU64, Ordering};
use proc_macro::{TokenStream, TokenTree, Literal};

static TYPE_ID_COUNTER: AtomicU64 = AtomicU64::new(0);

// Generates a new u64 each time it's called, starting at 0
#[proc_macro]
pub fn new_typeid(_: TokenStream) -> TokenStream {
    // Increment TYPE_ID_COUNTER each time this is called, returning the previous u64
    TokenTree::Literal(Literal::u64_suffixed(
        TYPE_ID_COUNTER.fetch_add(1, Ordering::Relaxed)
    )).into()
}

My original thought process was that, for each type used in a program, this code would generate an impl FlexibleAny with a unique TYPE_ID. In my further testing, this was not the case. Instead, new_typeid is only evaluated once and every single implementation returns a type of 0.

Do you have any ideas for another approach that would fix this? My best idea was to somehow pass type information (T) into new_typeid, but I don't know if that would be available during macro expansion. I imagine another form of reflection would help, but that sounds like it would require some rustc internals or a build hack.

Thank you for your help and not just saying "oh that's stupid, stop it." :slight_smile:

~ BD103

This is not an area I'm too familiar with and I'm largely free-associating in this reply, so ignore me when someone more knowledgeable comes along.


I think basically you need a way to sync your macro not across threads, but across compilation units, and perhaps even multiple compilation runs (linking). I believe there is no blessed way to do it, but that there are some cursed ways.

The main thing that came to mind was typetag, but I don't know if seeing how he does it will help you or not.

that's actually what I expect to happen reading you code. a blanket impl block might be checked many times during type check phase, but the block itself appears only once in the code, so the macro is indeed expanded once.

unfortunately I can't think of a way to do what you described without compiler builtins/intrinsics "magic". maybe a rustc plugin can do? but I know nothing about plugins, I can't say for sure.

Thank you for your suggestion, but I don't think typetag would fit my use-case. It depends on inventory, and thus ctor, which involves run-before-main code. This would defeat the purpose of evaluating during compile-time and seems just too complicated to get working.

Thank you anyways for your help :slight_smile:

1 Like

Thank you, but from what I can tell by rustc's source code, I think compiler plugins only really work for linting and cargo check. (Not to mention it is deprecated and requires nightly Rust.)

After doing some more research on trying make Any non-static, it seems that I'm trying to solve a problem I don't have. It seems that Any would need a lifetime parameter so you could downcast to a specific lifetime, which could be unsound.

Either way, thank you for your help. I think I'm going to try a different approach in my presentation than re-creating Any. Thanks!

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.