Поиск…
замечания
Прохождение макросов можно найти на языке программирования ржавчины (aka the Book) .
Руководство
Макросы позволяют нам абстрагировать синтаксические шаблоны, которые повторяются много раз. Например:
/// Computes `a + b * c`. If any of the operation overflows, returns `None`.
fn checked_fma(a: u64, b: u64, c: u64) -> Option<u64> {
let product = match b.checked_mul(c) {
Some(p) => p,
None => return None,
};
let sum = match a.checked_add(product) {
Some(s) => s,
None => return None,
};
Some(sum)
}
Заметим , что два match
заявления очень похожи: оба они имеют один и тот же шаблон
match expression {
Some(x) => x,
None => return None,
}
Представьте, что мы представляем вышеприведенный шаблон как try_opt!(expression)
, тогда мы могли бы переписать функцию только в 3 строки:
fn checked_fma(a: u64, b: u64, c: u64) -> Option<u64> {
let product = try_opt!(b.checked_mul(c));
let sum = try_opt!(a.checked_add(product));
Some(sum)
}
try_opt!
не может написать функцию, потому что функция не поддерживает раннее возвращение. Но мы могли бы сделать это с помощью макроса - всякий раз, когда у нас есть синтаксические шаблоны, которые невозможно представить с помощью функции, мы можем попытаться использовать макрос.
Мы определяем макрос с помощью macro_rules!
синтаксис:
macro_rules! try_opt {
// ^ note: no `!` after the macro name
($e:expr) => {
// ^~~~~~~ The macro accepts an "expression" argument, which we call `$e`.
// All macro parameters must be named like `$xxxxx`, to distinguish from
// normal tokens.
match $e {
// ^~ The input is used here.
Some(x) => x,
None => return None,
}
}
}
Это оно! Мы создали наш первый макрос.
(Попробуйте в Rust Playground )
Создание макроса HashSet
// This example creates a macro `set!` that functions similarly to the built-in
// macro vec!
use std::collections::HashSet;
macro_rules! set {
( $( $x:expr ),* ) => { // Match zero or more comma delimited items
{
let mut temp_set = HashSet::new(); // Create a mutable HashSet
$(
temp_set.insert($x); // Insert each item matched into the HashSet
)*
temp_set // Return the populated HashSet
}
};
}
// Usage
let my_set = set![1, 2, 3, 4];
Рекурсия
Макрос может вызывать себя, как функция рекурсии:
macro_rules! sum {
($base:expr) => { $base };
($a:expr, $($rest:expr),+) => { $a + sum!($($rest),+) };
}
Пойдем, хотя расширение sum!(1, 2, 3)
:
sum!(1, 2, 3)
// ^ ^~~~
// $a $rest
=> 1 + sum!(2, 3)
// ^ ^
// $a $rest
=> 1 + (2 + sum!(3))
// ^
// $base
=> 1 + (2 + (3))
Предел рекурсии
Когда компилятор слишком сильно расширяет макросы, он откажется. По умолчанию компилятор завершится с ошибкой после расширения макросов до 64 уровней, поэтому, например, следующее расширение приведет к сбою:
sum!(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,
21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,
41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62)
// error: recursion limit reached while expanding the macro `sum`
// --> <anon>:3:46
// 3 |> ($a:expr, $($rest:expr),+) => { $a + sum!($($rest),+) };
// |> ^^^^^^^^^^^^^^^^
Когда достигнут предел рекурсии, вы должны рассмотреть возможность реорганизации своего макроса, например
- Может быть, рекурсия может быть заменена повторением?
- Может быть, формат ввода может быть изменен на что-то менее фантастическое, поэтому нам не нужна рекурсия, чтобы соответствовать ему?
Если есть какая-то законная причина, недостаточно 64 уровней, вы всегда можете увеличить лимит ящика, ссылаясь на макрос с атрибутом:
#![recursion_limit="128"]
// ^~~ set the recursion limit to 128 levels deep.
Несколько шаблонов
Макрос может создавать различные выходные данные для разных шаблонов ввода:
/// The `sum` macro may be invoked in two ways:
///
/// sum!(iterator)
/// sum!(1234, iterator)
///
macro_rules! sum {
($iter:expr) => { // This branch handles the `sum!(iterator)` case
$iter.fold(0, |a, b| a + *b)
};
// ^ use `;` to separate each branch
($start:expr, $iter:expr) => { // This branch handles the `sum!(1234, iter)` case
$iter.fold($start, |a, b| a + *b)
};
}
fn main() {
assert_eq!(10, sum!([1, 2, 3, 4].iter()));
assert_eq!(23, sum!(6, [2, 5, 9, 1].iter()));
}
Спецификаторы фрагментов - вид шаблонов
В $e:expr
expr
называется спецификатором фрагмента . Он сообщает парсеру, какие маркеры ожидает параметр $e
. Rust предоставляет множество спецификаторов фрагментов, что позволяет вводить данные очень гибкими.
Тендерный | Описание | Примеры |
---|---|---|
ident | Идентификатор | x , foo |
path | Квалифицированное имя | std::collection::HashSet , Vec::new |
ty | Тип | i32 , &T , Vec<(char, String)> |
expr | выражение | 2+2 , f(42) , if true { 1 } else { 2 } |
pat | Шаблон | _ , c @ 'a' ... 'z' , (true, &x) , Badger { age, .. } |
stmt | утверждение | let x = 3 , return 42 |
block | Блок с разделителями | { foo(); bar(); } , { x(); y(); z() } |
item | Вещь | fn foo() {} , struct Bar; , use std::io; |
meta | Внутри атрибута | cfg!(windows) , doc="comment" |
tt | Токен | + , foo , 5 , [?!(???)] |
Обратите внимание, что комментарий doc comment /// comment
рассматривается так же, как #[doc="comment"]
для макроса.
macro_rules! declare_const_option_type {
(
$(#[$attr:meta])*
const $name:ident: $ty:ty as optional;
) => {
$(#[$attr])*
const $name: Option<$ty> = None;
}
}
declare_const_option_type! {
/// some doc comment
const OPT_INT: i32 as optional;
}
// The above will be expanded to:
#[doc="some doc comment"]
const OPT_INT: Option<i32> = None;
Следуйте установленному
Некоторым спецификаторам фрагментов требуется токен, следующий за ним, который должен быть одним из ограниченного набора, называемым «follow set». Это позволяет немного гибкости для синтаксиса Rust, чтобы развиваться без нарушения существующих макросов.
Тендерный | Следуйте установленному |
---|---|
expr , stmt | => , ; |
ty , path | => , = | ; : > [ { as , where |
pat | => , = | if in |
ident , block , item , meta , tt | любой токен |
macro_rules! invalid_macro {
($e:expr + $f:expr) => { $e + $f };
// ^
// `+` is not in the follow set of `expr`,
// and thus the compiler will not accept this macro definition.
($($e:expr)/+) => { $($e)/+ };
// ^
// The separator `/` is not in the follow set of `expr`
// and thus the compiler will not accept this macro definition.
}
Экспорт и импорт макросов
Экспорт макроса, чтобы другие модули могли его использовать:
#[macro_export]
// ^~~~~~~~~~~~~~~ Think of it as `pub` for macros.
macro_rules! my_macro { (..) => {..} }
Использование макросов из других ящиков или модулей:
#[macro_use] extern crate lazy_static;
// ^~~~~~~~~~~~ Must add this in order to use macros from other crates
#[macro_use] mod macros;
// ^~~~~~~~~~~~ The same for child modules.
Отладка макросов
(Все они нестабильны и поэтому могут использоваться только из ночного компилятора).
log_syntax! ()
#![feature(log_syntax)]
macro_rules! logged_sum {
($base:expr) => {
{ log_syntax!(base = $base); $base }
};
($a:expr, $($rest:expr),+) => {
{ log_syntax!(a = $a, rest = $($rest),+); $a + logged_sum!($($rest),+) }
};
}
const V: u32 = logged_sum!(1, 2, 3);
Во время компиляции он выведет на stdout следующее:
a = 1, остаток = 2, 3
a = 2, остаток = 3
base = 3
- Довольно расширенный
Запустите компилятор с помощью:
rustc -Z unstable-options --pretty expanded filename.rs
Это расширит все макросы, а затем распечатает расширенный результат в stdout, например, выше, вероятно, будет выводиться:
#![feature(prelude_import)]
#![no_std]
#![feature(log_syntax)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std as std;
const V: u32 = { false; 1 + { false; 2 + { false; 3 } } };
(Это похоже на флаг -E
в компиляторах gcc
и clang
.)