Let's play with Rust's Procedural Macro ... Reload
Powered by

> whoami michele.damico@gmail.com https://github.com/la10736 https://www.linkedin.com/in/damico/ @PhenoCoder

Perché?
- La Meta Programmazione è Strafiga?
- E' Molto Difficile?
- Possiamo Raccontarlo In Giro?
La Meta Programmazione è Strafiga?E' Molto Difficile?Possiamo Raccontarlo In Giro?
- Scrivere codice in Rust
- Togliere la magia
- Rust ha degli ottimi strumenti di Meta Programmazione
Come
Procedural macro
Custom derive
Function-like macro
Attribute macro
#[derive(Serialize)]
struct Message {
uid: u64,
data: String,
} my_macro!(first, second);macro_rules!
++
#[my_attribute]
fn some_function(arg1: i32, arg2: String) {
// Body
}Attribute macro
User code
use my_proc_macro_crate::my_attribute;
#[my_attribute(some_args)]
fn user_function(arg_1: u32) -> String {
// Body
}Proc macro crate
#[proc_macro_attribute]
pub fn my_attribute(
args: proc_macro::TokenStream,
input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
// Code that's modify input code and return it
}my_proc_macro_crate
AST
my_attribute
AST
Compiler
syn & quote
syn
quote
- Strutture dell' AST
- Trait Parse e funzioni di parse
- Info posizione codice
- visit, fold ... altre features opzionabili
struct MyData {
idents: Vec<Ident>
}
impl Parse for MyData {
fn parse(input: ParseStream) -> Result<Self> {
let idents = Punctuated::<Ident, Token![,]>::
parse_terminated(input)?;
Ok(MyData {
idents: idents.into_iter().collect(),
})
}
}
let data: MyData = parse(&tokens).unwrap();Macro quote! con la quale scrivere codice Rust usando anche variabili locali e ripetizioni che verra' trasformato nei token.
let sign_args = fn_args(fixture).take(n);
let fixture_args = fn_args_idents(fixture);
let name = Ident::new(&format!("partial_{}", n),
Span::call_site());
quote! {
#[allow(unused_mut)]
pub fn #name #generics (#(#sign_args),*) #output
#where_clause
{
#inject
Self::get(#(#fixture_args),*)
}
}How to use it...
Debug
> cargo install cargo-expand
> cargo expand
Good way
Raw way
println!("{}", tokens);
println!("{:#?}", tokens);
<- AST
Cosa ?
Un semplice clone di .....
pytest
..... O meglio implementiamo il concetto di fixture
Fixture
def test_dump_empty_repository(out, repository):
dump_repository(repository, out)
content = open(out).read()
assert content.strip() == "No Entries"
def test_dump_no_empty_repository(out, two_entries_repository):
dump_repository(two_entries_repository, out)
content = open(out).read()
assert "Michele, d'Amico : 44" in content
assert "John, Doe : 37" in content
assert 2 == len(content.splitlines())
@pytest.fixture
def repository():
return Repository()
@pytest.fixture
def out(tmpdir):
return os.path.join(tmpdir, "dump")
@pytest.fixture
def two_entries_repository(repository):
repository.insert(Person("Michele", "d'Amico", 44))
repository.insert(Person("John", "Doe", 37))
return repository
Desiderata
#[rstest]
fn dump_empty_repository<R: Repository>(out: &Path, repository: R) {
dump_repository(&repository, out);
let mut content = String::new();
File::open(&out).unwrap().read_to_string(&mut content).unwrap();
assert_eq!(content.trim(), "No Entries");
}
#[rstest]
fn dump_no_empty_repository<R: Repository>(out: &Path, two_entries_repository: R) {
dump_repository(&two_entries_repository, out);
let mut content = String::new();
File::open(&out).unwrap().read_to_string(&mut content).unwrap();
assert!(content.contains("Michele, d'Amico : 44"));
assert!(content.contains("John, Doe : 37"));
assert_eq!(2, content.lines().count());
}
Magari...
- Generics
-
&Path - Run parallelo
Fattibile
#[rstest]
fn dump_empty_repository(out: TempPath, repository: Repository) {
dump_repository(&repository, &out);
let mut content= String::new();
File::open(&out).unwrap().read_to_string(&mut content).unwrap();
assert_eq!(content.trim(), "No Entries");
}
#[rstest]
fn dump_no_empty_repository(out: TempPath, two_entries_repository: Repository) {
dump_repository(&two_entries_repository, &out);
let mut content = String::new();
File::open(&out).unwrap().read_to_string(&mut content).unwrap();
assert!(content.contains("Michele, d'Amico : 44"));
assert!(content.contains("John, Doe : 37"));
assert_eq!(2, content.lines().count());
}

L'idea...

Sostituire ogni argomento con una variabile locale inizializzata chiamando la funzione della fixture
#[test]
fn dump_empty_repository() {
let out = out();
let repository = repository();
dump_repository(&repository, &out);
let mut content = String::new();
File::open(&out).unwrap().read_to_string(&mut content).unwrap();
assert_eq!(content.trim(), "No Entries");
}
Hello proc_macro

Struttura
use hello_proc_macro::hello;
#[hello]
fn other(s: &str, v: i32) {
println!("other body <- '{}', {} ", s, v)
}
fn main() { other("pippo", 42) }
main.rs
[workspace]
members = [
"hello_proc_macro"
]
[dependencies]
hello_proc_macro = { path = "hello_proc_macro" }Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = {version="*", features=["full"]}
quote = "*"
proc-macro2 = "*"hello_proc_macro/Cargo.toml
Hello proc_macro (cont.)
#[proc_macro_attribute]
pub fn hello(
_attrs: proc_macro::TokenStream,
body: proc_macro::TokenStream
) -> proc_macro::TokenStream{
// Parse tokens
let ast = syn::parse(body).unwrap();
// Build impl
let gen = impl_hello_world(ast);
// Return the generated impl
gen.into()
}Core code:
fn impl_hello_world(
item: syn::ItemFn
) -> proc_macro2::TokenStream {
let vis = &item.vis;
let attrs = &item.attrs;
let sig = &item.sig;
let block = &item.block;
let args = item.sig.inputs.iter()
.filter_map(maybe_ident);
quote! {
#(#attrs)*
#vis #sig {
#(
println!("{} = {}", stringify!(#args), #args);
)*
#block
}
}
}Obbiettivo
use rstest::rstest;
fn fixture() -> &'static str {
"42"
}
fn fix_string() -> String {
"String".to_string()
}
#[rstest]
fn some_test(fixture: &str, fix_string: String) {
assert_eq!(fixture, "42");
assert_eq!(fix_string, "String".to_string());
}
Aggiungere un crate rstest al workspace con una macro procedurale rstest che
trasformi le fixture in variabili locali
Un po di link
- syn crate docs https://docs.rs/syn/1.0.14/syn
- syn repo https://github.com/dtolnay/syn
- quote https://docs.rs/quote/1.0.2/quote/
- quote macro https://docs.rs/quote/1.0.2/quote/macro.quote.html
- repo github esercizio http://github.com/la10736/rstest_playground
Ora iniziamo!

> git clone https://github.com/la10736/rstest_playground
Let's Play with Rust's Procedural Macro
By Michele D'Amico
Let's Play with Rust's Procedural Macro
Giochiamo con le macro procedurali per scrivere in clone di pytest
- 203