Макроси
05 декември 2024
Макроси
- метапрограмиране
- писане на код, който генерира друг код
Макроси
- метапрограмиране
- писане на код, който генерира друг код
Могат да се използват за:
// функции с произволен брой аргументи
println!("x={}, y={}", x, y);
// спестяване на писането на повтарящ се код
impl_trait_for_integers!(MyAdd, i8, i16, i32, i64, i128, isize);
// derive атрибути
#[derive(Clone, Debug, Default)]
struct MyStruct {}
// "декоратори" за функции
#[tracing::instrument]
pub fn my_function(my_arg: usize) { /* ... */ }
Декларативни макроси
- a.k.a. macros by example
macro_rules! name_of_macro {
($var1:expr, $var2:expr) => {
some_code($var1, $var2)
}
}
#![allow(unused_macros)] fn main() {} macro_rules! name_of_macro { ($var1:expr, $var2:expr) => { some_code($var1, $var2) } }
- прави pattern matching на ниво token-и
- извикването на макрото се замества с генерирания код
Декларативни макроси
macro_rules! add {
($var1:expr, $var2:expr) => {
$var1 + $var2
}
}
#![allow(unused_macros)] fn main() {} macro_rules! add { ($var1:expr, $var2:expr) => { $var1 + $var2 } }
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
- Всички тези скоби са взаимозаменяеми измежду кръгли, квадратни и къдрави скоби
- "Променливите"
$var1
,$var2
се наричат метапроменливи и в случая са от "тип" expression - цялостен израз
Примери
try!
macro_rules! try_ {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(e) => return Err(e.into()),
}
}
}
#![allow(unused_macros)] fn main () {} macro_rules! try_ { ($expr:expr) => { match $expr { Ok(value) => value, Err(e) => return Err(e.into()), } } }
Примери
add!
macro_rules! add {
($var1:expr, $var2:expr) => {
$var1 + $var2
}
}
fn main() {
println!("{}", add!(1, 1));
println!("{}", add!("foo".to_string(), "bar"));
}
2 foobar
macro_rules! add { ($var1:expr, $var2:expr) => { $var1 + $var2 } } fn main() { println!("{}", add!(1, 1)); println!("{}", add!("foo".to_string(), "bar")); }
Примери
add!
macro_rules! add {
(Чш, я събери ($var1:expr) и ($var2:expr)) => {
$var1 + $var2
}
}
fn main() {
println!("{}", add!(Чш, я събери (1) и (1)));
println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar")));
}
2 foobar
macro_rules! add { (Чш, я събери ($var1:expr) и ($var2:expr)) => { $var1 + $var2 } } fn main() { println!("{}", add!(Чш, я събери (1) и (1))); println!("{}", add!(Чш, я събери ("foo".to_string()) и ("bar"))); }
Примери
add!
Защо има скоби? За да се знае къде свършва изразa.
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_37ebf19d111716c51c4d834a542f32e8e0bbc423.rs:5:30 | 5 | (Чш, я събери $var1:expr и $var2:expr) => { | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;` error: could not compile `rust` (bin "main_37ebf19d111716c51c4d834a542f32e8e0bbc423") due to 1 previous error
#![allow(unused_macros)] fn main () {} macro_rules! add { (Чш, я събери $var1:expr и $var2:expr) => { $var1 + $var2 } }
Примери
add!
Непосредствено след expr са позволени само (=>
), (,
) и (;
), ако expr не е в скоби
macro_rules! add {
(Чш, я събери $var1:expr, $var2:expr) => {
$var1 + $var2
}
}
fn main() {
println!("{}", add!(Чш, я събери 1, 1));
println!("{}", add!(Чш, я събери "foo".to_string(), "bar"));
}
2 foobar
macro_rules! add { (Чш, я събери $var1:expr, $var2:expr) => { $var1 + $var2 } } fn main() { println!("{}", add!(Чш, я събери 1, 1)); println!("{}", add!(Чш, я събери "foo".to_string(), "bar")); }
Примери
map!
macro_rules! map {
// ???
}
fn main() {
let m = map! {
"a": 1,
"b": 2
};
println!("{:?}", m);
}
Повторения
$( ... ),*
-- repetition operator
Повторения
$( ... ),*
-- repetition operator- състои се от
$( ... )
и едно от трите:*
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
Повторения
$( ... ),*
-- repetition operator- състои се от
$( ... )
и едно от трите:*
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
- Може да сложим разделител веднага след затварящата скоба, например
,
Повторения
$( ... ),*
-- repetition operator- състои се от
$( ... )
и едно от трите:*
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
- Може да сложим разделител веднага след затварящата скоба, например
,
$( ... ),*
търси нещо от вида... , ... , ...
$( ... );*
търси нещо от вида... ; ... ; ...
Повторения
$( ... ),*
-- repetition operator- състои се от
$( ... )
и едно от трите:*
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
- Може да сложим разделител веднага след затварящата скоба, например
,
$( ... ),*
търси нещо от вида... , ... , ...
$( ... );*
търси нещо от вида... ; ... ; ...
- Операторът не поддържа optional trailing разделител
Повторения
macro_rules! map {
{
$( $key:expr : $value:expr ),*
} => {
// Забележете блока
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
Повторения
macro_rules! map {
{
$( $key:expr : $value:expr ),*
} => {
// Забележете блока
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
- има повторение
$( $key:expr : $value:expr ),*
в шаблона (pattern-а) - има повторение
$( map.insert($key, $value); )*
в резултата
Повторения
macro_rules! map {
{
$( $key:expr : $value:expr ),*
} => {
// Забележете блока
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
- има повторение
$( $key:expr : $value:expr ),*
в шаблона (pattern-а) - има повторение
$( map.insert($key, $value); )*
в резултата - повторенията могат да се влагат
- но всяка метапроменлива в резултата трябва да се използва на същата дълбочина, като в шаблона
Повторения
Окей, нека да компилираме
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);
}
error: `$key:expr` is followed by `:`, which is not allowed for `expr` fragments --> src/bin/main_085811e736fee0028063ec10a1b8ec346134f42a.rs:3:22 | 3 | $( $key:expr : $value:expr ),* | ^ not allowed after `expr` fragments | = note: allowed there are: `=>`, `,` or `;` error: could not compile `rust` (bin "main_085811e736fee0028063ec10a1b8ec346134f42a") due to 1 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); }
Повторения
Правилата са си правила. Ще използваме =>
вместо :
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);
}
{"b": 2, "a": 1}
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); }
Повторения
А ако искаме да поддържаме trailing comma 🤔
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);
}
error: unexpected end of macro invocation --> src/bin/main_8b44efda988c973dac4013461fc1752e839b7c67.rs:16:18 | 1 | macro_rules! map { | ---------------- when calling this macro ... 16 | "b" => 2, // <- тази запетайка | ^ missing tokens in macro arguments | note: while trying to match meta-variable `$key:expr` --> src/bin/main_8b44efda988c973dac4013461fc1752e839b7c67.rs:3:12 | 3 | $( $key: expr => $value: expr ),* | ^^^^^^^^^^ error: could not compile `rust` (bin "main_8b44efda988c973dac4013461fc1752e839b7c67") due to 1 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); }
Повторения
Можем да добавим група, която приема 0 или 1 запетайки
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)?
} => { // ^^^^^ -- тази група
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
fn main() {
let m1 = map! {
"a" => 1,
"b" => 2
};
let m2 = 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 m1 = map! { "a" => 1, "b" => 2 }; let m2 = map! { "a" => 1, "b" => 2, }; }
Разпъване
Макросите в Rust не се разпъват чрез просто заместване на текст, който след това се компилира.
C/C++ работят така и това води до неочаквани резултати
#define TIMES_FIVE(x) 5*x
int main() {
int val = TIMES_FIVE(2 + 3);
// = 5 * 2 + 3;
// = 13;
}
Разпъване
Макросите в Rust се разпъват след като е построено абстрактно синтактично дърво.
Заместват се node-ове в синтактичното дърво.
macro_rules! five_times {
($x:expr) => (5 * $x);
}
println!("{}", five_times!(2 + 3));
25
fn main() { macro_rules! five_times { ($x:expr) => (5 * $x); } println!("{}", five_times!(2 + 3)); }
Разпъване
┌─────────┐ ┌─────────┐
│ BinOp │ ┌╴│ LitInt │
│ op: Mul │ │ │ val: 5 │
│ lhs: ◌ │╶┘ └─────────┘
│ rhs: ◌ │╶┐ ┌─────────┐
└─────────┘ └╴│ $x │
│ │
└─────────┘
┌─────────┐ ┌─────────┐
│ BinOp │ ┌╴│ LitInt │
│ op: Mul │ │ │ val: 5 │
│ lhs: ◌ │╶┘ └─────────┘
│ rhs: ◌ │╶┐ ┌─────────┐ ┌─────────┐
└─────────┘ └╴│ BinOp │ ┌╴│ LitInt │
│ op: Add │ │ │ val: 2 │
│ lhs: ◌ │╶┘ └─────────┘
│ rhs: ◌ │╶┐ ┌─────────┐
└─────────┘ └╴│ LitInt │
│ val: 3 │
└─────────┘
Хигиена
- макросите в Rust са (частично) хигиенични
- променливи декларирани извън макрото имат един синтактичен контекст
- променливи декларирани в макрото имат друг синтактичен контекст
Хигиена
В този пример отново заради хигиена двата state
-а не се shadow-ват взаимно
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); }
Хигиена
В този пример отново заради хигиена двата state
-а не се shadow-ват взаимно
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); }
- все едно двете променливи имат различен цвят, който ги разграничава
Хигиена
Този пример не се компилира, защото двете a
-та имат различнен "цвят"
macro_rules! using_a {
($e:expr) => {
{
let a = 42;
$e
}
}
}
let four = using_a!(a / 10);
error[E0425]: cannot find value `a` in this scope --> src/bin/main_f39435f4cb6c956dce1c09e9930a82e63710566d.rs:11:21 | 11 | let four = using_a!(a / 10); | ^ not found in this scope For more information about this error, try `rustc --explain E0425`. error: could not compile `rust` (bin "main_f39435f4cb6c956dce1c09e9930a82e63710566d") due to 1 previous error
fn main() { macro_rules! using_a { ($e:expr) => { { let a = 42; $e } } } let four = using_a!(a / 10); }
Хигиена
Вместо това, трябва да подадем a
като метапроменлива от тип ident
macro_rules! using_a {
($a:ident, $e:expr) => {
{
let $a = 42;
$e
}
}
}
let four = using_a!(a, a / 10);
println!("four={}", four);
four=4
fn main() { macro_rules! using_a { ($a:ident, $e:expr) => { { let $a = 42; $e } } } let four = using_a!(a, a / 10); println!("four={}", four); }
Хигиена
По същата причина не можем да декларираме променливи, които се виждат извън макроса
macro_rules! declare_x {
() => {
let x = 3;
}
}
declare_x!();
println!("{}", x);
error[E0425]: cannot find value `x` in this scope --> src/bin/main_6e5c34cef4a01ff0cdb6c9e4451bd4d88ee8b4df.rs:9:16 | 9 | println!("{}", x); | ^ not found in this scope For more information about this error, try `rustc --explain E0425`. error: could not compile `rust` (bin "main_6e5c34cef4a01ff0cdb6c9e4451bd4d88ee8b4df") due to 1 previous error
macro_rules! declare_x { () => { let x = 3; } } fn main() { declare_x!(); println!("{}", x); }
Хигиена
Освен ако не подадем името на променливата на макроса
macro_rules! declare_var {
($var:ident) => {
let $var = 3;
}
}
declare_var!(x);
println!("{}", x);
3
macro_rules! declare_var { ($var:ident) => { let $var = 3; } } fn main() { declare_var!(x); println!("{}", x); }
Хигиена
- хигиената важи за променливи
- но не и за lifetime анотации и item-и
- типове, трейтове, функции, модули, …
macro_rules! declare_foo {
() => {
fn foo() { println!("macros!") }
}
}
declare_foo!();
foo();
macros!
macro_rules! declare_foo { () => { fn foo() { println!("macros!") } } } fn main() { declare_foo!(); foo(); }
Синтаксис
Извикване на макроси
- Макросите могат да се извикват с кръгли, квадратни или къдрави скоби
foo!( ... );
foo![ ... ];
foo! { ... }
Синтаксис
Извикване на макроси
- Макросите могат да се извикват с кръгли, квадратни или къдрави скоби
foo!( ... );
foo![ ... ];
foo! { ... }
- вида на скобите няма значение
Синтаксис
Извикване на макроси
- Макросите могат да се извикват с кръгли, квадратни или къдрави скоби
foo!( ... );
foo![ ... ];
foo! { ... }
- вида на скобите няма значение
- макросите трябва да съдържат само валидни Rust token-и
Синтаксис
Извикване на макроси
- Макросите могат да се извикват с кръгли, квадратни или къдрави скоби
foo!( ... );
foo![ ... ];
foo! { ... }
- вида на скобите няма значение
- макросите трябва да съдържат само валидни Rust token-и
- скобите в макросите трябва да са балансирани т.е.
foo!([)
е невалидно
Синтаксис
Metavariables & Fragment specifiers
Всички възможни типове на метапроменливи може да намерите в The Rust Reference
Ограниченията кои токени могат да стоят след кой тип метапроменливи също се намира на същата страница
Синтаксис
Ръкави
Макросите могат да имат повече от един ръкав за matching разделени с ;
macro_rules! my_macro {
($e: expr) => (...);
($i: ident) => (...);
(for $i: ident in $e: expr) => (...);
}
- пробват се подред
- няма backtracking
- ако компилатора успее да match-не началото на ръкав, но не и края, ще върне компилационна грешка
- няма да пробва с останалите ръкави, дори и някой от тях да би match-нал успешно
- т.е. ръкавите трябва да имат уникален префикс
Рекурсия
Макросите могат да извикват други макроси и дори себе си както този прост HTML shorthand
Дъното на рекурсията е най-отгоре, защото опитите за match-ване започват от първия към последния ръкав
macro_rules! write_html {
($w: expr, ) => (());
($w: expr, $e: tt) => (writeln!($w, "{}", $e)?);
($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
writeln!($w, "<{}>", stringify!($tag))?;
write_html!($w, $($inner)*);
writeln!($w, "</{}>", stringify!($tag))?;
write_html!($w, $($rest)*);
}};
}
#![allow(unused_macros)] fn main() {} macro_rules! write_html { ($w: expr, ) => (()); ($w: expr, $e: tt) => (writeln!($w, "{}", $e)?); ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{ writeln!($w, "<{}>", stringify!($tag))?; write_html!($w, $($inner)*); writeln!($w, "{}>", stringify!($tag))?; write_html!($w, $($rest)*); }}; }
Рекурсия
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) => (writeln!($w, "{}", $e)?); ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{ writeln!($w, "<{}>", stringify!($tag))?; write_html!($w, $($inner)*); writeln!($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(()) }
Рекурсия
Разгъване по стъпки
macro_rules! write_html {
($w: expr, ) => (());
($w: expr, $e: tt) => (writeln!($w, "{}", $e)?);
($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*) => {{
writeln!($w, "<{}>", stringify!($tag))?;
write_html!($w, $($inner)*);
writeln!($w, "</{}>", stringify!($tag))?;
write_html!($w, $($rest)*);
}};
}
write_html!(&mut out, html[head[title["Macros guide"]] body[h1["Macros are the best!"]]])
// ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*)
{
writeln!(out, "<{}>", stringify!(html))?;
write_html!(out, head[title["Macros guide"]] body[h1["Macros are the best!"]]);
writeln!(out, "</{}>", stringify!(html))?;
}
// ($w: expr, $tag: ident [ $( $inner: tt )* ] $( $rest: tt )*)
{
writeln!(out, "<{}>", stringify!(html))?;
{
writeln!(out, "<{}>", stringify!(head))?;
write_html!(out, title["Macros guide"]);
writeln!(out, "</{}>", stringify!(head))?;
write_html!(out, body[h1["Macros are the best!"]]);
}
writeln!(out, "</{}>", stringify!(html))?;
}
// ...
Scoping
Mакросите имат специфични правила за видимост
Scoping
Mакросите имат специфични правила за видимост
- Видимостта на макрос е след дефиницията му - в същия scope и в child mods
Scoping
Mакросите имат специфични правила за видимост
- Видимостта на макрос е след дефиницията му - в същия scope и в child mods
- Използването на макрос от друг модул става чрез
#[macro_use]
преди мястото, където го ползвате
Scoping
Имаме макроси дефинирани в macros и ще ги използваме в client
#[macro_use]
mod macros;
mod client; // ок
mod client; // компилационна грешка
#[macro_use]
mod macros;
Scoping
При работа на ниво crate
Scoping
При работа на ниво crate
- се използва
#[macro_use]
за импортиране на всичко или#[macro_use(my_macro, other_macro)]
Scoping
При работа на ниво crate
- се използва
#[macro_use]
за импортиране на всичко или#[macro_use(my_macro, other_macro)]
- за да направите макросите достъпни за други crate-ове се използва
#[macro_export]
Scoping
Имаме макроси дефинирани в macros и ще ги използваме в client
// crate macros
mod some_module {
#[macro_export]
macro_rules! hello {
() => (println!("Hello!"))
}
}
// crate client
#[macro_use]
extern crate macros;
fn main() {
hello!();
}
Scoping
Или по-лесният вариант - като нормален import
// crate macros
mod some_module {
#[macro_export]
macro_rules! hello {
() => (println!("Hello!"))
}
}
// crate client
// notice top-level use
use macros::hello;
fn main() {
hello!();
}
Scoping
Внимавайте с видимостта на типовет, които използвате.
$crate
обозначава крейта, в който е дефиниран макросът.
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)
};
}
#[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
Дебъгването на макроси е сложно, но има някои полезни команди
rustup toolchain add nightly
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustup toolchain add nightly
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustup toolchain add nightly
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
cargo +nightly rustc --profile=check -- -Zunpretty=expanded,hygiene
запазва синтактичните scope-ове
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
rustup toolchain add nightly
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
cargo +nightly rustc --profile=check -- -Zunpretty=expanded,hygiene
запазва синтактичните scope-ове
Или може да изполваме extension за cargo
cargo install cargo-expand
cargo expand
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищо
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищоtrace_macros!(true)
- включва компилаторни съобщения при разгръщане на макрос
Debugging
Има и удобни, но нестабилни макроси, които се ползват през feature gate на nightly
log_syntax!(...)
- принтира аргументите си при компилация на stdout и се разгръща до нищоtrace_macros!(true)
- включва компилаторни съобщения при разгръщане на макросtrace_macros!(false)
- изключва съобщенията
Материали
- The Little Book of Rust Macros
- много точно и пълно обяснение как работят макросите
- както и някои по-сложни примери
Процедурни макроси
- декларативните макроси се пишат лесно, но са ограничени
Процедурни макроси
- декларативните макроси се пишат лесно, но са ограничени
- regex-like pattern matching по синтактичното дърво
Процедурни макроси
- декларативните макроси се пишат лесно, но са ограничени
- regex-like pattern matching по синтактичното дърво
- процедурните макроси нямат такива ограничения
Процедурни макроси
- декларативните макроси се пишат лесно, но са ограничени
- regex-like pattern matching по синтактичното дърво
- процедурните макроси нямат такива ограничения
- процедурния макрос е Rust функция, която приема
TokenStream
и връщаTokenStream
Процедурни макроси
- Работят директо с TokenStream - приемат и връщат TokenStream
- Всеки процедурен макрос е отделна Rust библиотека (нормалните макроси си имат собствен синтаксис чрез
macro_rules!
) - Не са хигиенични - за разлика от нормалните макроси се повлияват от кода около тях
Процедурни макроси
Видове
- Function-like macros -
sql!(SELECT * FROM posts WHERE id=1)
- Derive macros -
#[derive(CustomDerive)]
- Attribute macros -
#[CustomAttribute]
Процедурни макроси
За да създадем процедурен макрос ни трябва нов crate, в чиито манифест да се съдържа
[lib]
proc-macro = true
Процедурни макроси
use proc_macro::TokenStream;
#[proc_macro]
pub fn my_macro(tokens: TokenStream) -> TokenStream {
todo!()
}
Процедурни макроси
syn and quote
Обикновенно не е удобно да се работи директно с TokenStream
, затова се използват два пакета:
syn
предоставя парсър, който превръща TokenStream
в синтактично дърво AST (Abstract syntax tree)
quote
предоставя начин да превърнем синтактичното дърво обратно в TokenStream
, който да върнем на компилатора