Макроси

22 ноември 2022

Административни неща

Макроси

Каква е целта им?

Макроси

Каква е целта им?

Генериране на код преди компилация

try!

Това вече сме го виждали, макар и за кратко

1 2 3 4 5 6 7 8
macro_rules! try {
    ($expr:expr) => {
        match $expr {
            Ok(value) => value,
            Err(e) => return Err(e.into()),
        }
    }
}

try!

Това вече сме го виждали, макар и за кратко

1 2 3 4 5 6 7 8
macro_rules! try {
    ($expr:expr) => {
        match $expr {
            Ok(value) => value,
            Err(e) => return Err(e.into()),
        }
    }
}

В новите версии на Rust се използва специалният синтаксис ? и try е запазена дума
Но това е добър пример за лесен макрос

add!

Общата схема

1 2 3 4 5 6 7 8 9 10
macro_rules! add {
    ($var1:expr, $var2:expr) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(1, 1));
    println!("{}", add!("foo".to_string(), "bar"));
}
warning: trailing semicolon in macro used in expression position --> src/bin/main_6b0f96a7a5d598d3bdb6421c0e289d9f4728daec.rs:3:22 | 3 | $var1 + $var2; | ^ ... 8 | println!("{}", add!(1, 1)); | ---------- in this macro invocation | = note: `#[warn(semicolon_in_expressions_from_macros)]` on by default = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info) warning: trailing semicolon in macro used in expression position --> src/bin/main_6b0f96a7a5d598d3bdb6421c0e289d9f4728daec.rs:3:22 | 3 | $var1 + $var2; | ^ ... 9 | println!("{}", add!("foo".to_string(), "bar")); | ------------------------------ in this macro invocation | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info)
macro_rules! add {
    ($var1:expr, $var2:expr) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(1, 1));
    println!("{}", add!("foo".to_string(), "bar"));
}

add!

Общата схема

add!

Общата схема

add!

Общата схема

add!

Общата схема

add!

Общата схема

add!

Общата схема

add!

Защо не "променливи"? Защото в кръглите скоби се прави pattern-matching на ниво token-и:

1 2 3 4 5 6 7 8 9 10
macro_rules! add {
    (Чш, я събери ($var1:expr) и ($var2:expr)) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(Чш, я събери (1) и (1)));
    println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar")));
}
warning: trailing semicolon in macro used in expression position --> src/bin/main_f57be85444c112b49502f6b6f21f168461e01b3b.rs:3:22 | 3 | $var1 + $var2; | ^ ... 8 | println!("{}", add!(Чш, я събери (1) и (1))); | ---------------------------- in this macro invocation | = note: `#[warn(semicolon_in_expressions_from_macros)]` on by default = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info) warning: trailing semicolon in macro used in expression position --> src/bin/main_f57be85444c112b49502f6b6f21f168461e01b3b.rs:3:22 | 3 | $var1 + $var2; | ^ ... 9 | println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar"))); | ------------------------------------------------ in this macro invocation | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info)
macro_rules! add {
    (Чш, я събери ($var1:expr) и ($var2:expr)) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(Чш, я събери (1) и (1)));
    println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar")));
}

add!

Защо има скоби? За да се знае къде свършва expression/израз.

1 2 3 4 5
macro_rules! add {
    (Чш, я събери $var1:expr и $var2:expr) => {
        $var1 + $var2;
    }
}
error: `$var1:expr` is followed by `и`, which is not allowed for `expr` fragments --> src/bin/main_be9c85d3e58c64ae0ab6a9978ece02a78cdf0563.rs:5:30 | 5 | (Чш, я събери $var1:expr и $var2:expr) => { | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;` error: could not compile `rust` due to previous error
#![allow(unused_macros)]
fn main () {}
macro_rules! add {
    (Чш, я събери $var1:expr и $var2:expr) => {
        $var1 + $var2;
    }
}

add!

Непосредствено след expr са позволени само (=>), (,) и (;), ако expr не е в скоби

1 2 3 4 5 6 7 8 9 10
macro_rules! add {
    (Чш, я събери $var1:expr, $var2:expr) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(Чш, я събери 1, 1));
    println!("{}", add!(Чш, я събери "foo".to_string(), "bar"));
}
warning: trailing semicolon in macro used in expression position --> src/bin/main_5d8abaa0152280df2b24f0f3bd1c67815970799e.rs:3:22 | 3 | $var1 + $var2; | ^ ... 8 | println!("{}", add!(Чш, я събери 1, 1)); | ----------------------- in this macro invocation | = note: `#[warn(semicolon_in_expressions_from_macros)]` on by default = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info) warning: trailing semicolon in macro used in expression position --> src/bin/main_5d8abaa0152280df2b24f0f3bd1c67815970799e.rs:3:22 | 3 | $var1 + $var2; | ^ ... 9 | println!("{}", add!(Чш, я събери "foo".to_string(), "bar")); | ------------------------------------------- in this macro invocation | = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! = note: for more information, see issue #79813 <https://github.com/rust-lang/rust/issues/79813> = note: this warning originates in the macro `add` (in Nightly builds, run with -Z macro-backtrace for more info)
macro_rules! add {
    (Чш, я събери $var1:expr, $var2:expr) => {
        $var1 + $var2;
    }
}

fn main() {
    println!("{}", add!(Чш, я събери 1, 1));
    println!("{}", add!(Чш, я събери "foo".to_string(), "bar"));
}

map!

Нещо малко по-практично

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! map {
    {
        $( $key: expr : $value: expr ),*
    } => {
        // Забележете блока
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Какво прави $( ... ),* ?

map!

Ок, нека да компилираме

1 2 3 4 5 6 7 8 9 10 11
macro_rules! map {
    {
        $( $key: expr : $value: expr ),*
    } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}

map!

Ок, нека да компилираме

1 2 3 4 5 6 7 8 9 10 11
macro_rules! map {
    {
        $( $key: expr : $value: expr ),*
    } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}
error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments --> src/bin/main_48aecfc760527ed5f683faae57a6895a4507076a.rs:6:23 | 6 | $( $key: expr : $value: expr ),* | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;` error: could not compile `rust` due to previous error
#![allow(unused_macros)]
fn main() {}
macro_rules! map {
    {
        $( $key: expr : $value: expr ),*
    } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}

map!

Правилата са си правила… Ще ги разгледаме подробно по-късно

1 2 3 4 5 6 7 8 9 10 11
macro_rules! map {
    {
        $( $key: expr => $value: expr ),*
    } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}
#![allow(unused_macros)]
fn main() {}
macro_rules! map {
    {
        $( $key: expr => $value: expr ),*
    } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    }
}

map!

1 2 3 4 5 6
let m = map! {
    "a" => 1,
    "b" => 2
};

println!("{:?}", m);
{"a": 1, "b": 2}
macro_rules! map {
{
$( $key: expr => $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
fn main() {
let m = map! {
    "a" => 1,
    "b" => 2
};

println!("{:?}", m);
}

map!

А какво става, ако искаме да поддържаме trailing comma 🤔

1 2 3 4 5 6
let m = map! {
    "a" => 1,
    "b" => 2,
};

println!("{:?}", m);

map!

А какво става, ако искаме да поддържаме trailing comma 🤔

1 2 3 4 5 6
let m = map! {
    "a" => 1,
    "b" => 2,
};

println!("{:?}", m);
error: unexpected end of macro invocation --> src/bin/main_5c32cc6affa79f7fd4a16843f01c100a2bb3f65e.rs:15:14 | 1 | macro_rules! map { | ---------------- when calling this macro ... 15 | "b" => 2, | ^ missing tokens in macro arguments error: could not compile `rust` due to previous error
macro_rules! map {
{
$( $key: expr => $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
fn main() {
let m = map! {
    "a" => 1,
    "b" => 2,
};

println!("{:?}", m);
}

map!

Не точно каквото очаквахме..

map!

Може би така?

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! map {
    {
        $( $key: expr => $value: expr ),*,
    } => {
        /* ... */
    }
}

let m = map! {
    "a" => 1,
    "b" => 2
};

map!

Може би така?

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! map {
    {
        $( $key: expr => $value: expr ),*,
    } => {
        /* ... */
    }
}

let m = map! {
    "a" => 1,
    "b" => 2
};
error: unexpected end of macro invocation --> src/bin/main_069557a786e297d4c06486f2019e02e452864ede.rs:17:13 | 1 | macro_rules! map { | ---------------- when calling this macro ... 17 | "b" => 2 | ^ missing tokens in macro arguments error: could not compile `rust` due to previous error
macro_rules! map {
    {
        $( $key: expr => $value: expr ),*,
    } => {
        /* ... */
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
    }
}

fn main() {
let m = map! {
    "a" => 1,
    "b" => 2
};
}

map!

Не..

map!

Не бойте се, има си трик за това

Преди време се налагаше да правим следното, може да го видите в legacy код

1 2 3 4 5 6 7
macro_rules! map {
    {
        $( $key: expr => $value: expr ),* $(,)*
    } => {
        /* ... */
    }
}

map!

Недостатъка е, че може да match-нем нещо такова

1 2 3 4
let m = map! {
    "a" => 1,
    "b" => 2,,,,,,,,,,,,
};
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
fn main() {
let m = map! {
    "a" => 1,
    "b" => 2,,,,,,,,,,,,
};
}

map!

Но както казахме има оператор ?

1 2 3 4 5 6 7
macro_rules! map {
    {
        $( $key: expr => $value: expr ),* $(,)?
    } => {
        /* ... */
    }
}

map!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
macro_rules! map {
    {
        $( $key: expr => $value: expr ),* $(,)?
    } => {
        /* ... */
    }
}

map! {
    "a" => 1,
    "b" => 2
};

map! {
    "a" => 1,
    "b" => 2,
};
macro_rules! map {
    {
        $( $key: expr => $value: expr ),* $(,)?
    } => {
        /* ... */
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
    }
}

fn main() {
map! {
    "a" => 1,
    "b" => 2
};

map! {
    "a" => 1,
    "b" => 2,
};
}

Хигиена

Макросите в Rust са хигиенични

1 2 3 4 5
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

println!("{}", five_times!(2 + 3));

Хигиена

Макросите в Rust са хигиенични

1 2 3 4 5
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

println!("{}", five_times!(2 + 3));
25
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

fn main() {
println!("{}", five_times!(2 + 3));
}

Хигиена

Макросите в Rust са хигиенични

1 2 3 4 5
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

println!("{}", five_times!(2 + 3));
25
macro_rules! five_times {
    ($x:expr) => (5 * $x);
}

fn main() {
println!("{}", five_times!(2 + 3));
}

Нещо подобно в C/C++ би изчислило 13

Хигиена

В този пример отново заради хигиена двата state-а не се shadow-ват взаимно

1 2 3 4 5 6 7 8 9 10 11
macro_rules! log {
    ($msg:expr) => {{
        let state: i32 = get_log_state();
        if state > 0 {
            println!("log({}): {}", state, $msg);
        }
    }};
}

let state: &str = "reticulating splines";
log!(state);
log(1): reticulating splines
fn get_log_state() -> i32 { 1 }
macro_rules! log {
    ($msg:expr) => {{
        let state: i32 = get_log_state();
        if state > 0 {
            println!("log({}): {}", state, $msg);
        }
    }};
}

fn main() {
let state: &str = "reticulating splines";
log!(state);
}

Хигиена

Всяко разгъване на макрос се случва в различен синтактичен контекст.
В този случай може да го мислите все едно двете променливи имат различен цвят който ги разграничава.

Хигиена

По тази причина не може да представяме нови променливи чрез макрос по следния начин

1 2 3 4 5 6
macro_rules! foo {
    () => (let x = 3;);
}

foo!();
println!("{}", x);
error[E0425]: cannot find value `x` in this scope --> src/bin/main_f77bccafcffc077ab7e68cffcdade87e2b6d1674.rs:7:16 | 7 | println!("{}", x); | ^ not found in this scope For more information about this error, try `rustc --explain E0425`. error: could not compile `rust` due to previous error
macro_rules! foo {
    () => (let x = 3;);
}

fn main() {
foo!();
println!("{}", x);
}

Хигиена

Ще трябва да подадем името на променлива на макроса за да се получи

1 2 3 4 5 6
macro_rules! foo {
    ($v:ident) => (let $v = 3;);
}

foo!(x);
println!("{}", x);
3
macro_rules! foo {
    ($v:ident) => (let $v = 3;);
}

fn main() {
foo!(x);
println!("{}", x);
}

Хигиена

Правило важи за let и цикли като loop while for, но не и за item-и, което значи, че следното ще се компилира

1 2 3 4 5 6
macro_rules! foo {
    () => (fn x() { println!("macros!") });
}

foo!();
x();
macros!
macro_rules! foo {
    () => (fn x() { println!("macros!") });
}

fn main() {
foo!();
x();
}

Синтаксис

Извикване на макроси

Макросите следват същите правила както останалата част от синтаксиса на Rust

Синтаксис

Извикване на макроси

Макросите следват същите правила както останалата част от синтаксиса на Rust

Синтаксис

Извикване на макроси

Макросите следват същите правила както останалата част от синтаксиса на Rust

Синтаксис

Извикване на макроси

Макросите следват същите правила както останалата част от синтаксиса на Rust

Синтаксис

Синтаксис

Синтаксис

Синтаксис

Синтаксис

Формално извикването на макрос се състои от поредица от token trees които са

Синтаксис

Формално извикването на макрос се състои от поредица от token trees които са

Синтаксис

Формално извикването на макрос се състои от поредица от token trees които са

Синтаксис

Затова Rust макросите винаги приоритизират затварянето на скобите пред match-ването, което е полезно при някои подходи за match-ване

Синтаксис

Metavariables & Fragment specifiers

Tиповете на метапроменливите са:

Синтаксис

Metavariables & Fragment specifiers

Ограниченията за типовете са:

Ръкави

Макросите могат да имат повече от един ръкав за matching разделени с ;

1 2 3 4 5
macro_rules! my_macro {
    ($e: expr) => (...);
    ($i: ident) => (...);
    (for $i: ident in $e: expr) => (...);
}

Ръкави

Има и конвенция за private ръкави @text, които да се викат чрез рекурсия
Това не е официален синтаксис, а по-скоро нещо което общността на Rust е приела за стандартно

1 2 3 4 5
macro_rules! my_macro {
    (for $i: ident in $e: expr) => (...);
    (@private1 $e: expr) => (...);
    (@private2 $i: ident) => (...);
}

Рекурсия

Макросите могат да извикват други макроси и дори себе си както този прост HTML shorthand

Дъното на рекурсията е най-отгоре, защото опитите за match-ване започват от първия към последния ръкав

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! write_html {
    ($w: expr, ) => (());

    ($w: expr, $e: tt) => (write!($w, "{}", $e)?);

    ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
        write!($w, "<{}>", stringify!($tag))?;
        write_html!($w, $($inner)*);
        write!($w, "</{}>", stringify!($tag))?;
        write_html!($w, $($rest)*);
    }};
}
#![allow(unused_macros)]
fn main() {}
macro_rules! write_html {
    ($w: expr, ) => (());

    ($w: expr, $e: tt) => (write!($w, "{}", $e)?);

    ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
        write!($w, "<{}>", stringify!($tag))?;
        write_html!($w, $($inner)*);
        write!($w, "", stringify!($tag))?;
        write_html!($w, $($rest)*);
    }};
}

Рекурсия

1 2 3 4 5 6 7 8 9 10 11 12 13
use std::fmt::Write;

let mut out = String::new();

write_html! {
    &mut out,
    html[
        head[title["Macros guide"]]
        body[h1["Macros are the best!"]]
    ]
}

println!("{}", out);
<html><head><title>Macros guide</title></head><body><h1>Macros are the best!</h1></body></html>
macro_rules! write_html {
($w: expr, ) => (());

($w: expr, $e: tt) => (write!($w, "{}", $e)?);

($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
write!($w, "<{}>", stringify!($tag))?;
write_html!($w, $($inner)*);
write!($w, "", stringify!($tag))?;
write_html!($w, $($rest)*);
}};
}
fn main() -> Result<(), ::std::fmt::Error> {
use std::fmt::Write;

let mut out = String::new();

write_html! {
    &mut out,
    html[
        head[title["Macros guide"]]
        body[h1["Macros are the best!"]]
    ]
}

println!("{}", out);
Ok(())
}

Рекурсия

Може да срещнете legacy алтернатива на ?, чрез използване на ръкави

1 2 3 4 5 6 7 8 9 10 11 12 13
macro_rules! map {
    { $( $key: expr => $value: expr ),*, } => {
        map!( $( $key => $value ),* );
    };

    { $( $key: expr => $value: expr ),* } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    };
}
#![allow(unused_macros)]
fn main() {}
macro_rules! map {
    { $( $key: expr => $value: expr ),*, } => {
        map!( $( $key => $value ),* );
    };

    { $( $key: expr => $value: expr ),* } => {
        {
            let mut map = ::std::collections::HashMap::new();
            $( map.insert($key, $value); )*
            map
        }
    };
}

Scoping

Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост

Scoping

Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост

Scoping

Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост

Scoping

Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост

Scoping

Имаме макроси дефинирани в macros и ще ги използваме в client

1 2 3
#[macro_use]
mod macros;
mod client; // ок

1 2 3
mod client; // компилационна грешка
#[macro_use]
mod macros;

Scoping

При работа на ниво crate

Scoping

При работа на ниво crate

Scoping

При работа на ниво crate

Scoping

Имаме макроси дефинирани в macros и ще ги използваме в client

1 2 3 4 5 6 7 8
// crate macros

mod some_module {
    #[macro_export]
    macro_rules! hello {
        () => (println!("Hello!"))
    }
}
1 2 3 4 5 6 7 8
// crate client

#[macro_use]
extern crate macros;

fn main() {
    hello!();
}

Scoping

Или по-лесният вариант - като нормален import

1 2 3 4 5 6 7 8
// crate macros

mod some_module {
    #[macro_export]
    macro_rules! hello {
        () => (println!("Hello!"))
    }
}
1 2 3 4 5 6 7 8
// crate client

// notice top-level use
use macros::hello;

fn main() {
    hello!();
}

Scoping

Макроси дефинирани в блокове, функции или други подобни конструкции са видими само там

1 2 3
fn main() {
    macro_rules! map { ... }
}

Scoping

Внимавайте с видимостта на типовет, които използвате.
$crate обозначава крейта, в който е дефиниран макросът.

1 2 3 4 5 6 7 8 9 10 11 12 13
macro_rules! try {
    ($expr:expr) => {
        match $expr {
            $crate::result::Result::Ok(val) => val,
            $crate::result::Result::Err(err) => {
                return $crate::result::Result::Err($crate::convert::From::from(err));
            }
        }
    };
    ($expr:expr,) => {
        $crate::try!($expr)
    };
}
error: expected identifier, found reserved keyword `try` --> src/bin/main_8aab1807f55927f0c9c23c61185da78e6cb53b2e.rs:3:14 | 3 | macro_rules! try { | ^^^ expected identifier, found reserved keyword | help: escape `try` to use it as an identifier | 3 | macro_rules! r#try { | ++ error: could not compile `rust` due to previous error
#[allow(unused_macros)]
macro_rules! try {
    ($expr:expr) => {
        match $expr {
            $crate::result::Result::Ok(val) => val,
            $crate::result::Result::Err(err) => {
                return $crate::result::Result::Err($crate::convert::From::from(err));
            }
        }
    };
    ($expr:expr,) => {
        $crate::try!($expr)
    };
}
fn main() {}

Debugging

Дебъгването на макроси е сложно, но има някои полезни команди

Debugging

Дебъгването на макроси е сложно, но има някои полезни команди

Debugging

Дебъгването на макроси е сложно, но има някои полезни команди

Debugging

Дебъгването на макроси е сложно, но има някои полезни команди

Или може да изполваме extension за cargo

1 2
cargo install cargo-expand
cargo expand

Debugging

Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly

Debugging

Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly

Debugging

Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly

Debugging

Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly

Стандартни макроси

Стандартни макроси

Стандартни макроси

Стандартни макроси

Стандартни макроси

Advanced

TT Muncher (TokenTree Muncher или само Token Muncher)

Рекурсивно чете токени от подадените аргументи.
Може да чете токен по токен, като ги разпределя в различни ръкави.

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! write_html {
    ($w: expr, ) => (());

    ($w: expr, $e: tt) => (write!($w, "{}", $e)?);

    ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
        write!($w, "<{}>", stringify!($tag))?;
        write_html!($w, $($inner)*);
        write!($w, "</{}>", stringify!($tag))?;
        write_html!($w, $($rest)*);
    }};
}
#![allow(unused_macros)]
fn main() {}
macro_rules! write_html {
    ($w: expr, ) => (());

    ($w: expr, $e: tt) => (write!($w, "{}", $e)?);

    ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
        write!($w, "<{}>", stringify!($tag))?;
        write_html!($w, $($inner)*);
        write!($w, "", stringify!($tag))?;
        write_html!($w, $($rest)*);
    }};
}

Advanced

TT Muncher

1 2 3 4 5 6 7
write_html! {
    &mut out,
    html[
        head[title["Macros guide"]]
        body[h1["Macros are the best!"]]
    ]
}

Advanced

Push-Down Accumulation

Акумулира токени, за да се използват в някой от следващите рекурсивни повиквания.

Пример за макрос, който инициализира масив до 3 елемента

1 2 3 4 5 6 7 8 9 10 11 12 13 14
macro_rules! init_array {
    [$e:expr; $n:tt] => {{
        let e = $e;
        init_array!(@accum ($n, e.clone()) -> ())
    }};
    (@accum (3, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (2, $e) -> ($($body)* $e,)) };
    (@accum (2, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (1, $e) -> ($($body)* $e,)) };
    (@accum (1, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (0, $e) -> ($($body)* $e,)) };
    (@accum (0, $_e:expr) -> ($($body:tt)*)) => { init_array!(@as_expr [$($body)*]) };
    (@as_expr $e:expr) => { $e };
}

let strings: [String; 3] = init_array![String::from("hi!"); 3];
println!("{:?}", strings);
["hi!", "hi!", "hi!"]
macro_rules! init_array {
    [$e:expr; $n:tt] => {{
        let e = $e;
        init_array!(@accum ($n, e.clone()) -> ())
    }};
    (@accum (3, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (2, $e) -> ($($body)* $e,)) };
    (@accum (2, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (1, $e) -> ($($body)* $e,)) };
    (@accum (1, $e:expr) -> ($($body:tt)*)) => { init_array!(@accum (0, $e) -> ($($body)* $e,)) };
    (@accum (0, $_e:expr) -> ($($body:tt)*)) => { init_array!(@as_expr [$($body)*]) };
    (@as_expr $e:expr) => { $e };
}

fn main() {
let strings: [String; 3] = init_array![String::from("hi!"); 3];
println!("{:?}", strings);
}

Advanced

Push-Down Accumulation

А не може ли да опростим нещата до това?

1 2 3 4 5 6 7 8 9 10 11 12
macro_rules! init_array {
    (@accum 0, $_e:expr) => {/* empty */};
    (@accum 1, $e:expr) => {$e};
    (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
    (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
    [$e:expr; $n:tt] => {
        {
            let e = $e;
            [ init_array!(@accum $n, e) ]
        }
    };
}

Advanced

Push-Down Accumulation

Не…

1 2 3 4 5 6 7 8 9 10 11 12 13 14
macro_rules! init_array {
    (@accum 0, $_e:expr) => {/* empty */};
    (@accum 1, $e:expr) => {$e};
    (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
    (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
    [$e:expr; $n:tt] => {
        {
            let e = $e;
            [ init_array!(@accum $n, e) ]
        }
    };
}

let strings: [String; 3] = init_array![String::from("hi!"); 3];
error: macro expansion ignores token `,` and any following --> src/bin/main_d7893d0d02408938f95722ad3842b5c665d42a1e.rs:5:31 | 5 | (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)}; | ^ ... 9 | [ init_array!(@accum $n, e) ] | -------------------------- help: you might be missing a semicolon here: `;` | | | caused by the macro expansion here | = note: the usage of `init_array!` is likely invalid in expression context error[E0308]: mismatched types --> src/bin/main_d7893d0d02408938f95722ad3842b5c665d42a1e.rs:9:13 | 9 | [ init_array!(@accum $n, e) ] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected an array with a fixed size of 3 elements, found one with 1 element ... 15 | let strings: [String; 3] = init_array![String::from("hi!"); 3]; | ----------------------------------- in this macro invocation | = note: expected array `[String; 3]` found array `[String; 1]` = note: this error originates in the macro `init_array` (in Nightly builds, run with -Z macro-backtrace for more info) For more information about this error, try `rustc --explain E0308`. error: could not compile `rust` due to 2 previous errors
macro_rules! init_array {
    (@accum 0, $_e:expr) => {/* empty */};
    (@accum 1, $e:expr) => {$e};
    (@accum 2, $e:expr) => {$e, init_array!(@accum 1, $e)};
    (@accum 3, $e:expr) => {$e, init_array!(@accum 2, $e)};
    [$e:expr; $n:tt] => {
        {
            let e = $e;
            [ init_array!(@accum $n, e) ]
        }
    };
}

fn main() {
let strings: [String; 3] = init_array![String::from("hi!"); 3];
}

Advanced

Push-Down Accumulation

…защото това би довело до следното разгъване

1 2 3 4 5
init_array!(@accum 3, e)
e, init_array!(@accum 2, e)
e, e, init_array!(@accum 1, e)
e, e, e
[e, e, e]

Тук всяка помощна стъпка ще е невалиден Rust синтаксис и това не е позволено независимо от стъпките

Advanced

Push-Down Accumulation

Push-Down ни позволява да правим подобни конструкции чрез акумулиране на токени, без да се налага да имаме валиден синтаксис през цялото време.

Advanced

Push-Down Accumulation

Разгъвка на първия пример изглежда така

1 2 3 4 5 6
init_array! { String:: from ( "hi!" ) ; 3 }
init_array! { @ accum ( 3 , e.clone() ) -> (  ) }
init_array! { @ accum ( 2 , e.clone() ) -> ( e.clone() , ) }
init_array! { @ accum ( 1 , e.clone() ) -> ( e.clone() , e.clone() , ) }
init_array! { @ accum ( 0 , e.clone() ) -> ( e.clone() , e.clone() , e.clone() , ) }
init_array! { @ as_expr [ e.clone() , e.clone() , e.clone() , ] }

Advanced

Push-Down Accumulation се използва в комбинация с TT Muncher, за да се парсват произволно сложни граматики

Материали

Въпроси