Макроси
22 ноември 2022
Административни неща
- Домашното приключва след два дни
Макроси
Каква е целта им?
Макроси
Каква е целта им?
Генериране на код преди компилация
try!
Това вече сме го виждали, макар и за кратко
macro_rules! try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(e) => return Err(e.into()),
}
}
}
try!
Това вече сме го виждали, макар и за кратко
macro_rules! try {
($expr:expr) => {
match $expr {
Ok(value) => value,
Err(e) => return Err(e.into()),
}
}
}
В новите версии на Rust се използва специалният синтаксис ?
и try
е запазена дума
Но това е добър пример за лесен макрос
add!
Общата схема
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!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
- Всички тези скоби са взаимозаменяеми измежду кръгли, квадратни и къдрави скоби
add!
Общата схема
macro_rules!
всъщност не е макро, а е "syntax extension", имплементирано на ниво компилатор- Името на макроса следвано от чифт скоби за тялото на макроса:
macro_rules! add { ... }
- Аргументи в скоби, последвани от стрелкичка и още един чифт скоби:
(...) => { ... }
- Всички тези скоби са взаимозаменяеми измежду кръгли, квадратни и къдрави скоби
- "Променливите"
$var1
,$var2
се наричат метапроменливи и в случая са от "тип" expression - цялостен израз
add!
Защо не "променливи"? Защото в кръглите скоби се прави pattern-matching на ниво token-и:
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/израз.
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 не е в скоби
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!
Нещо малко по-практично
macro_rules! map {
{
$( $key: expr : $value: expr ),*
} => {
// Забележете блока
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
map!
Какво прави $( ... ),*
?
map!
Какво прави $( ... ),*
?
- Това е repetition operator
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите:
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
$( ... ),*
търси нещо от вида... , ... , ...
map!
Какво прави $( ... ),*
?
- Това е repetition operator
- Винаги се състои от
$( ... )
и едно от трите: *
- 0 или повече повторения+
- 1 или повече повторения?
- 0 или 1 повторения- Може да сложим разделител веднага след затварящата скоба например
,
$( ... ),*
търси нещо от вида... , ... , ...
- Операторът не поддържа optional trailing разделител
map!
Ок, нека да компилираме
macro_rules! map {
{
$( $key: expr : $value: expr ),*
} => {
{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
}
}
map!
Ок, нека да компилираме
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!
Правилата са си правила… Ще ги разгледаме подробно по-късно
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!
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 🤔
let m = map! {
"a" => 1,
"b" => 2,
};
println!("{:?}", m);
map!
А какво става, ако искаме да поддържаме trailing comma 🤔
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!
Може би така?
macro_rules! map {
{
$( $key: expr => $value: expr ),*,
} => {
/* ... */
}
}
let m = map! {
"a" => 1,
"b" => 2
};
map!
Може би така?
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 код
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)*
} => {
/* ... */
}
}
map!
Недостатъка е, че може да match-нем нещо такова
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!
Но както казахме има оператор ?
macro_rules! map {
{
$( $key: expr => $value: expr ),* $(,)?
} => {
/* ... */
}
}
map!
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 са хигиенични
macro_rules! five_times {
($x:expr) => (5 * $x);
}
println!("{}", five_times!(2 + 3));
Хигиена
Макросите в Rust са хигиенични
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 са хигиенични
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-ват взаимно
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); }
Хигиена
Всяко разгъване на макрос се случва в различен синтактичен контекст.
В този случай може да го мислите все едно двете променливи имат различен цвят който ги разграничава.
Хигиена
По тази причина не може да представяме нови променливи чрез макрос по следния начин
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); }
Хигиена
Ще трябва да подадем името на променлива на макроса за да се получи
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-и, което значи, че следното ще се компилира
macro_rules! foo {
() => (fn x() { println!("macros!") });
}
foo!();
x();
macros!
macro_rules! foo { () => (fn x() { println!("macros!") }); } fn main() { foo!(); x(); }
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
foo![ ... ];
Синтаксис
Извикване на макроси
Макросите следват същите правила както останалата част от синтаксиса на Rust
foo!( ... );
foo![ ... ];
foo! { ... }
Синтаксис
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
- Скобите в макросите трябва да са балансирани т.е.
foo!([)
е невалидно
Синтаксис
- Макросите трябва да съдържат само валидни Rust token-и
- Скобите в макросите трябва да са балансирани т.е.
foo!([)
е невалидно - Без това ограничение, Rust няма как да знае къде свършва извикването на макроса
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
- произволна поредица от token trees обградена от
()
,[]
или{}
Синтаксис
Формално извикването на макрос се състои от поредица от token trees които са
- произволна поредица от token trees обградена от
()
,[]
или{}
- всеки друг единичен token
Синтаксис
Затова Rust макросите винаги приоритизират затварянето на скобите пред match-ването, което е полезно при някои подходи за match-ване
Синтаксис
Metavariables & Fragment specifiers
Tиповете на метапроменливите са:
ident
: an identifier.x
;foo
path
: a qualified name.T::SpecialA
expr
: an expression.2 + 2
;if true { 1 } else { 2 }
;f(42)
ty
: a type.i32
;Vec<(char, String)>
;&T
pat
: a pattern.Some(t)
;(17, 'a')
;_
pat_param
: a pattern (edition 2021). Supports nested|
.stmt
: a single statement.let x = 3
block
: a brace-delimited sequence of statements and optionally an expression.{ log(error, "hi"); return 12; }
item
: an item.fn foo() { }
;struct Bar;
meta
: a "meta item", as found in attributes.cfg(target_os = "windows")
tt
: a single token tree.lifetime
: a lifetime'a
.vis
: a possibly empty Visibility qualifier.literal
: literal expression.
Синтаксис
Metavariables & Fragment specifiers
Ограниченията за типовете са:
expr
andstmt
may only be followed by one of:=>
,,
, or;
.pat
andpat_param
may only be followed by one of:=>
,,
,=
,|
,if
, orin
.path
andty
may only be followed by one of:=>
,,
,=
,|
,;
,:
,>
,>>
,[
,{
,as
,where
, or a macro variable ofblock
fragment specifier.vis
may only be followed by one of:,
, an identifier other than a non-raw priv, any token that can begin a type, or a metavariable with aident
,ty
, orpath
fragment specifier.- All other fragment specifiers have no restrictions.
Ръкави
Макросите могат да имат повече от един ръкав за matching разделени с ;
macro_rules! my_macro {
($e: expr) => (...);
($i: ident) => (...);
(for $i: ident in $e: expr) => (...);
}
Ръкави
Има и конвенция за private ръкави @text
, които да се викат чрез рекурсия
Това не е официален синтаксис, а по-скоро нещо което общността на Rust е приела за стандартно
macro_rules! my_macro {
(for $i: ident in $e: expr) => (...);
(@private1 $e: expr) => (...);
(@private2 $i: ident) => (...);
}
Рекурсия
Макросите могат да извикват други макроси и дори себе си както този прост HTML shorthand
Дъното на рекурсията е най-отгоре, защото опитите за match-ване започват от първия към последния ръкав
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)*); }}; }
Рекурсия
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 алтернатива на ?
, чрез използване на ръкави
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
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
- Затова видимостта на макрос е след дефиницията му - в същия scope и в child mods
Scoping
Компилатора разгъва макросите в ранна фаза на компилация, затова имат специфична видимост
- Дефинициите и разгръщанията се случват в едно depth-first lexical-order обхождане на crate-a
- Затова видимостта на макрос е след дефиницията му - в същия 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
Макроси дефинирани в блокове, функции или други подобни конструкции са видими само там
fn main() {
macro_rules! map { ... }
}
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)
};
}
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
Дебъгването на макроси е сложно, но има някои полезни команди
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
cargo +nightly rustc --profile=check -- -Zunpretty=expanded
cargo +nightly rustc --profile=check -- -Zunpretty=expanded,hygiene
запазва синтактичните scope-ове
Debugging
Дебъгването на макроси е сложно, но има някои полезни команди
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)
- изключва съобщенията
Стандартни макроси
Стандартни макроси
panic!
- панира програмата
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементи
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементиassert!
&assert_eq!
- използват се при тестове за проверка на данните
Стандартни макроси
panic!
- панира програматаvec!
- създава вектор от елементиassert!
&assert_eq!
- използват се при тестове за проверка на данните- https://doc.rust-lang.org/stable/std/#macros
Advanced
TT Muncher (TokenTree Muncher или само Token Muncher)
Рекурсивно чете токени от подадените аргументи.
Може да чете токен по токен, като ги разпределя в различни ръкави.
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
write_html! {
&mut out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
]
}
Advanced
Push-Down Accumulation
Акумулира токени, за да се използват в някой от следващите рекурсивни повиквания.
Пример за макрос, който инициализира масив до 3 елемента
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
А не може ли да опростим нещата до това?
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
Не…
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
…защото това би довело до следното разгъване
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
Разгъвка на първия пример изглежда така
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, за да се парсват произволно сложни граматики