Closed dtolnay closed 5 years ago
Thanks for putting this project together!
See here for my quick implementation (with example) of this: https://github.com/kardeiz/objekt-clonable.
Actually, it is short so I'll paste it below.
This does handle the case where the objekt
crate has been renamed, but does require that the objekt
macros are loaded elsewhere.
#[proc_macro_attribute]
pub fn clonable(_attrs: TokenStream, item: TokenStream) -> TokenStream {
let mut item_trait = parse_macro_input!(item as ItemTrait);
let item_trait_ident = &item_trait.ident;
let random = rand::random::<u64>();
let objekt_rename = &Ident::new(&format!("_objekt_{}", random), Span::call_site());
let cloneish_paths: Vec<Path> = vec![
parse_quote!(Clone),
parse_quote!(std::clone::Clone),
parse_quote!(::std::clone::Clone),
];
if let Some(path) = item_trait
.supertraits
.iter_mut()
.filter_map(|x| match x {
TypeParamBound::Trait(ref mut y) => Some(y),
_ => None
})
.map(|x| &mut x.path)
.find(|x| cloneish_paths.iter().any(|y| &y == x))
{
*path = parse_quote!(#objekt_rename::Clone);
} else {
panic!("`Clone` must be present in trait supertrait list");
}
(quote!{
extern crate objekt as #objekt_rename;
#item_trait
clone_trait_object!(#item_trait_ident);
})
.into()
}
Thanks, this is terrific! Some comments, and then I'll check this item off the list:
Generally it's better for macros to be deterministic so I would avoid the random number. Nondeterminism makes it harder to do correct incremental compilation and it interferes with anyone diffing cargo expand
output before and after a change.
I would like to avoid requiring the objekt
dependency in the end user code since the point is to reduce number of steps.
I think the way to do this is move your procedural macro to a separate crate like objekt-clonable-impl, then make objekt-clonable a non proc-macro crate that contains:
#[doc(hidden)]
pub use objekt;
// Re-export the attribute macro.
pub use objekt_clonable_impl::*;
Now your macro can expand to code that refers to objekt_clonable::objekt::Clone
and objekt_clonable::objekt::clone_trait_object!
through only the single objekt-clonable import.
Separately, I'm not all that happy with the "objekt" name. Go ahead and pick something better for your crate if you have ideas. Something straightforward like clone_trait::clonable could work, or maybe some simple memorable name.
Thanks for the feedback!
I got rid of the random
, though I am still doing the rename (by appending the trait ident to the crate name).
I had to pub use objekt::__internal_clone_trait_object;
in the reexport crate to get the internal macro into scope.
I kind of like the name, but I'll think about the name also.
Uh-oh, leaking the internal macro needs to be fixed in my crate. I hadn't updated it since 2018-style macro imports were stabilized. I published objekt 0.1.2 with the fix so that should no longer be needed. Please set your Cargo.toml to require objekt = "0.1.2"
.
In the generated code I would skip the extern crate entirely and directly invoke objekt_clonable::objekt::clone_trait_object!
.
Oh, wow, okay that works!
I was worried about not being able to access the crate if it was renamed (in extern crate
or Cargo.toml), but I guess that isn't an issue anymore with the 2018 edition. I haven't completely switched mentally over yet to the new edition.
I've updated to objekt 0.1.2
.
Great. Please remove the dependency on rand and publish the crates to crates.io! Then I will add a link from the objekt readme and close out this issue.
Done: https://crates.io/crates/objekt-clonable!
By the way, thanks so much for serde, syn, and quote; they are fantastic! I think it is equally awesome that you've put this project together and are helping mentor people.
Added a link in the objekt readme! https://github.com/dtolnay/objekt/commit/f76af10143014365ec55c4cb8f77b86b5b6f45d1 Marked the idea as completed! https://github.com/dtolnay/request-for-implementation#procedural-macros
Nicely done.
The
objekt
crate provides a type-safe way to make trait objects clonable, but there are one too many steps involved.objekt::Clone
instead of plainClone
in the supertrait list;clone_trait_object!
macro.Ideally this would be taken care of by an attribute macro in one step that expands to the two steps above.
The attribute macro would assert that
Clone
is present in the supertrait list (or else fail to compile with an error), substitute thatClone
withobjekt::Clone
, and then make the correct call toclone_trait_object!
to emit the trait object impl.