Let's Play With Procedural Macro
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?
- Vogliamo scrivere codice in Rust
- Togliamo la magia
- Rust Difficile ma ha degli ottimi strumenti di Meta Programmazione
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");
}
Come
Usando le macro procedurali (Attribute-like): proc_macro
- Fn (tokens_attr, tokens_body) -> tokens_result
- La funzione puo' essere usata come attributo e viene chiamata dal parser
- Ogni panic lanciato dalla funzione viene intercettato e mostrato come errore all'utente
- unstable (anche chi usa deve usare nightly e abilitarle)
- Scarsa documentazione
- Supporto Stackoveflow quasi nullo
- Tokens -> Ast -> Modifica Ast -> Tokens
Problemini:
Hello proc_macro

Struttura
#![feature(proc_macro)]
extern crate hello_proc_macro;
use hello_proc_macro::hello;
#[hello]
fn other(_s: &str, _v: i32) {}
fn main() { other("pippo", 42); }
main.rs
[workspace]
[dependencies]
hello_proc_macro = { path = "hello_proc_macro" }Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = {version="*", features=["full"]}
quote = "*"hello_proc_macro/Cargo.toml
Hello proc_macro (cont.)
#![feature(proc_macro)]
// Omiss ....
#[proc_macro_attribute]
pub fn hello(_attrs: TokenStream, body: TokenStream) -> TokenStream{
// Parse the string representation
let ast = syn::parse(body).unwrap();
// Build the impl
let gen = impl_hello_world(ast);
// Return the generated impl
println!("RESULT CODE -> {}", gen);
gen.into()
}
fn impl_hello_world(mut ast: syn::Item) -> quote::Tokens {
if let syn::Item::Fn(ref mut item_fn) = ast {
let args = &item_fn.decl.inputs;
let stmt = format!(
r#"println!("Hello Word!!! : args tokens = {:?}");"#,
args.clone().into_tokens()
);
item_fn.block.stmts.insert(0, syn::parse_str(&stmt).unwrap());
}
ast.into_tokens()
}
Core code:
Obbiettivo
#![feature(proc_macro)]
extern crate rstest;
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
Tips:
- la macro quote!{} puo' essere utile per scrivere il risultato finale
- potrebbe essere piu' semplice lasciare il codice originale in un blocco scritto dopo le variabili
Un po di link
Ora iniziamo!

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