How to do it...

  1. Create a new sub-crate for the custom derive with cargo new chapter-five-derive.

  2. Open the newly generated chapter-five-derive/Cargo.toml.
  3. Add this directly above the [dependencies] section of the file in order to mark the crate as a procedural macro crate:
[lib]
proc-macro = true
  1. Under [dependencies], add the following lines:
syn = "0.11.11"
quote = "0.3.15"

If you want, you can go to the crates.io web pages for syn (https://crates.io/crates/syn) and quote (https://crates.io/crates/quote) to check for the newest version and use that one instead.

  1.  In the chapter-five-derive/src/lib.rs file, delete the generated code and add the following:
1    extern crate proc_macro; 
2 #[macro_use]
3 extern crate quote;
4 extern crate syn;
5
6 use proc_macro::TokenStream;
7
8 // HelloWorld is the name for the derive
9 // hello_world_name is the name of our optional attribute
10 #[proc_macro_derive(HelloWorld, attributes(hello_world_name))]
11 pub fn hello_world(input: TokenStream) -> TokenStream {
12 // Construct a string representation of the type definition
13 let s = input.to_string();
14 // Parse the string representation into an abstract syntax
tree
15 let ast = syn::parse_derive_input(&s).expect("Failed to
parse the source into an AST");
16
17 // Build the implementation
18 let gen = impl_hello_world(&ast);
19
20 // Return the generated implementation
21 gen.parse()
22 .expect("Failed to parse the AST generated from deriving
from HelloWorld")
23 }
24
25 fn impl_hello_world(ast: &syn::DeriveInput) -> quote::Tokens {
26 let identifier = &ast.ident;
27 // Use the name provided by the attribute
28 // If there is no attribute, use the identifier
29 let hello_world_name =
get_name_attribute(ast).unwrap_or_else(||
identifier.as_ref());
30 quote! {
31 // Insert an implementation for our trait
32 impl HelloWorld for #identifier {
33 fn hello_world() {
34 println!(
35 "The struct or enum {} says: "Hello world from
{}!"",
36 stringify!(#identifier),
37 #hello_world_name
38 );
39 } //end of fn hello_world()
40 } //end of impl HelloWorld
41 } //end of quote
42 } //end of fn impl_hello_world
43
44 fn get_name_attribute(ast: &syn::DeriveInput) -> Option<&str> {
45 const ATTR_NAME: &str = "hello_world_name";
46
47 // Go through all attributes and find one with our name
48 if let Some(attr) = ast.attrs.iter().find(|a| a.name() ==
ATTR_NAME) {
49 // Check if it's in the form of a name-value pair
50 if let syn::MetaItem::NameValue(_, ref value) = attr.value
{
51 // Check if the value is a string
52 if let syn::Lit::Str(ref value_as_str, _) = *value {
53 Some(value_as_str)
54 } else {
55 panic!(
56 "Expected a string as the value of {}, found {:?}
instead",
57 ATTR_NAME, value
58 );
59 }
60 } else {
61 panic!(
62 "Expected an attribute in the form #[{} = "Some
value"]",
63 ATTR_NAME
64 );
65 }
66 } else {
67 None
68 }
69 }
  1. In the original Cargo.toml file of this chapter, add the following to the [dependencies]:
custom-derive = { path = "custom-derive" }

               7. In the bin folder, create a file called custom_derive.rs.

               8. Add the following code and run it with cargo run --bin custom_derive:

1    #[macro_use]
2 extern crate chapter_five_derive;
3
4 // trait definitions have to be in "consumer" crate
5 trait HelloWorld {
6 // This method will send a friendly greeting
7 fn hello_world();
8 }
9
10 // thanks to the code in the custom_derive crate
11 // we can derive from HelloWorld in order to provide
12 // an automatic implementation for the HelloWorld trait
13 #[derive(HelloWorld)]
14 struct Switzerland;
15
16 #[derive(HelloWorld)]
17 struct Britain;
18
19 #[derive(HelloWorld)]
20 // We can use an optional attribute to change the message
21 #[hello_world_name = "the Land Down Under"]
22 struct Australia;
23
24 fn main() {
25 Switzerland::hello_world();
26 Britain::hello_world();
27 Australia::hello_world();
28 }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.123.155