dtolnay / async-trait

Type erasure for async trait methods
Apache License 2.0
1.84k stars 85 forks source link

Fix hygiene in expansion #164

Closed chipsenkbeil closed 3 years ago

chipsenkbeil commented 3 years ago

Resolves #163.

taiki-e commented 3 years ago

Note that this breaks compatibility with no-std environment.

See also https://github.com/dtolnay/async-trait/issues/132.

chipsenkbeil commented 3 years ago

@taiki-e, should there be another test environment to support the no-std environment? Would be good for this to be caught in automated test.

Reading through the issue, you mentioned a trick is needed to get this to work. I don't work outside of the std environment, so am not familiar with what would need to be done here. Would be happy to help if there's some good reading material to get me caught up.

I've been bitten by async-trait in quite a few cases where I'm trying to be hygienic, but have to manually include a reference to Box because of the lack of complete pathing. Hoping to help this get resolved.

taiki-e commented 3 years ago

Reading through the issue, you mentioned a trick is needed to get this to work. I don't work outside of the std environment, so am not familiar with what would need to be done here. Would be happy to help if there's some good reading material to get me caught up.

The basic idea is to import the alloc crate in front of the trait/impl when expanding the code and refer that crate.

extern crate alloc as _alloc;
trait ...
    fn method() -> ::_alloc::boxed::Box<...> // <- instead of `Box`

However, this way may result in crate name collisions (multiple _alloc in the same scope).

One of the workarounds for collisions is using the crate name that contains the hash value of the input tokens instead of _alloc. Something like:

#[proc_macro_attribute]
pub fn async_trait(args: ..., input: ...) -> ... {
    // ...
    let alloc_crate = format_ident!("_alloc{}", hash(&input));
    // ...
    quote! {
        extern crate alloc as #alloc_crate;
    }
}

use std::{collections::hash_map::DefaultHasher, hash::Hasher};

fn hash(input: &TokenStream) -> u64 {
    let mut hasher = DefaultHasher::new();
    hasher.write(input.to_string().as_bytes());
    hasher.finish()
}