|
Макрос это ф-ция которая принимает кусок кода и возвращает кусок кода
Почему println! это макрос? Потому-то произвольное количество аргументов Rust функции не могут принимать.
Пример:
macro_rules! console_log {
($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
}
// Простая реализация функции `log` если нет `crate log`
fn log(s: &str) {
println!("{}", s);
}
fn main() {
let name = "Rustacean";
let age = 42;
// Вызов макроса с разными аргументами
console_log!("Привет из Rust!");
console_log!("Привет, {}! Тебе {} лет.", name, age);
console_log!("Ответ: {}.", 42);
}
|
|
|
Подключение из модуля
Загружены могут быть только макросы, объявленные с атрибутом #[macro_export].
или use с 1.3v
#[macro_use(foo, bar)]
extern crate baz;
fn main(){}
Подключение внутри, и снаружи библиотеки
чтобы определить один макрос, который будет работать и внутри, и снаружи библиотеки
#[macro_export]
macro_rules! inc {
($x:expr) => ( $crate::increment($x) )
}
fn main(){}
|
|
|
Глава 1 — знакомит с метапрограммированием. В ней говорится о том, когда следует использовать макросы, а также о том, когда следует обратиться к более базовым строительным блокам Rust, таким как функции и структуры.
В главе 2 — рассматриваются декларативные макросы, начиная с основ, а затем переходя к примерам и вариантам использования.
Глава 3 — это когда мы переходим к процедурным макросам. Мы пишем наш первый макрос derive, который генерирует простой метод «Hello, World».
Глава 4 — учит читателя использовать макросы атрибутов для изменения полей структуры.
В главе 5 — показаны гибкие и мощные макросы, подобные функциям, — последний тип процедурных макросов — в действии.
В главе 6 — рассказывается о (модульном) тестировании макроса, в качестве основного примера используется макрос derive, который генерирует построитель.
Глава 7 — поможет вам понять обработку ошибок и предоставление пользователям обратной связи об ошибках.
Глава 8 — возвращается к примеру конструктора из главы 6, чтобы показать с его помощью, как можно использовать атрибуты, чтобы сделать макрос более гибким.
Глава 9 — позволяет читателю написать предметно-ориентированный язык, который может создать реальную инфраструктуру в облаке.
Глава 10 — завершается рассказом читателю о флагах функций, документировании, публикации и возможных следующих шагах.
Эта книга посвящена детальному объяснению работы с макросами в языке Rust и ориентирована на разработчиков, которые хотят глубже понять и использовать макросы для улучшения качества и эффективности кода. Основные темы книги включают:
Основы макросов — Как работают макросы в Rust, чем они отличаются от макросов в других языках, таких как C/C++.
Синтаксис и структура макросов — Основы написания макросов, правила шаблонов и синтаксический разбор.
Рекурсивные макросы — Как создавать макросы, которые могут вызывать сами себя, для более сложных логик.
Использование макросов для генерации кода — Как можно с помощью макросов генерировать повторяющиеся блоки кода, тем самым повышая производительность и снижая количество ошибок.
Примеры реальных сценариев — Различные кейсы использования макросов в реальных проектах, включая примеры кода и объяснение их работы.
Продвинутые темы — Работа с атрибутами, процедурными макросами и настройка рабочего окружения для разработки сложных макросов.
|
|
|
Пишешь DSL или часто генерируешь однотипный код? AST-макросы в Nim — проще и мощнее, чем procedural macros в Rust
Метапрограммирование в Lisp и гибкое изменение языка под задачу
Rust, при всей его мощности, предоставляет макросы как инструмент генерации кода, но не как способ изменить грамматику или поведение языка.
|
|
|
Расширяются в исходный код, который впоследствии компилируется с остальной частью программы.
(Однако, в отличие от макросов на C и других языках, макросы Rust расширяются в абстрактные синтаксические деревья, а не в подстановку строк, поэтому Вы не получаете неожиданных ошибок приоритета операций.)
Решает проблему приоритета операций из-за того что не вставляет код каждый элемент служит узлом синтаксического дерева, и решает проблему захвата переменной (переопределение) потому что каждая переменная обладает меткой контекста синтаксиса, где она была введена.
|
|
crate syn
Анализ исходного кода Rust
crate quote
Квазикавычурное цитирование Rust (полезно для интерполяции сгенерированного кода с помощью буквального кода)
crate paste
Объединение и манипулирование идентификаторами
crate darling
Макрос Derive для простого анализа входных данных макроса Derive
|
|
|
Развернуть макрос в Playground
в Playground, нажав на Tools > Expand Macros и открыть вкладку Macro Expansion
|
use tokio;
async fn async_give_8() -> u8 {
8
}
#[tokio::main]
async fn main() {
let some_number = async_give_8().await;
}
разворачивается в:
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
use tokio;
async fn async_give_8() -> u8 { 8 }
fn main() {
let body = async { let some_number = async_give_8().await; };
#[allow(clippy :: expect_used, clippy :: diverging_sub_expression)]
{
return tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("Failed building the Runtime").block_on(body);
}
}
|
|
разворачивает макрос для показа
dtolnay/cargo-expand
|
sudo mount -o remount,exec /tmp/
cargo install cargo-expand
cargo expand --theme=TwoDark // cargo expand main --theme=TwoDark
просмотр расширенного кода макроса
$ rustc +nightly -Zunpretty=expanded hello.rs
// или
$ cargo expand
|
|
Дебаг кода cargo-expand раскрывает код и макрос
cargo install cargo-expand
cargo expand > /home/jeka/file.rs
cargo expand --tests --lib > /home/jeka/file.r
cargo expand --lib example
|
cargo install cargo-expand
File src/lib.rs:
pub mod example{
use frame_support::parameter_types;
parameter_types! {
pub const MockBlockHashCount: u64 = 250;
}
}
fn foo(){}
Результат:
$ cargo expand --lib example
pub mod example {
use frame_support::parameter_types;
pub struct MockBlockHashCount;
impl MockBlockHashCount {
/// Returns the value of this parameter type.
pub const fn get() -> u64 {
250
}
}
impl<I: From<u64>> ::frame_support::traits::Get<I> for MockBlockHashCount {
fn get() -> I {
I::from(250)
}
}
}
File src/main.rs
#[derive(Debug)]
struct S;
fn main() {
println!("{:?}", S);
}
Результат:
$ cargo expand
#![feature(prelude_import)]
#[prelude_import]
use std::prelude::v1::*;
#[macro_use]
extern crate std;
struct S;
#[automatically_derived]
#[allow(unused_qualifications)]
impl ::core::fmt::Debug for S {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match *self {
S => {
let mut debug_trait_builder = f.debug_tuple("S");
debug_trait_builder.finish()
}
}
}
}
fn main() {
{
::std::io::_print(::core::fmt::Arguments::new_v1(
&["", "\n"],
&match (&S,) {
(arg0,) => [::core::fmt::ArgumentV1::new(arg0, ::core::fmt::Debug::fmt)],
},
));
};
}
|
|
Так почему же макросы полезны?
|
Основная причина использования макросов — избегать повторяющегося кода, особенно повторяющегося кода, который в противном случае пришлось бы вручную синхронизировать с другими частями кода.
Используйте макрос всякий раз, когда это единственный способ гарантировать синхронизацию разрозненного кода
(допустим всем модулям потребуется реализация трейта)
Не повторяйтесь.
Есть много случаев, когда вам может понадобиться подобная функциональность в нескольких местах, но с различными типами. Часто пишут макрос - это полезный способ избежать повторения кода, который невозможно объединить в функцию или обобщенный код.
Предметно ориентированные языки.
Макросы позволяют определить специальный синтаксис для конкретной цели.
Вариативные интерфейсы.
Иногда требуется определить интерфейс, который имеет переменное количество аргументов.
Пример: println!, который может принять любое количество аргументов в зависимости от строки формата.
|
|
|
Макросы в C/C++: В этих языках макросы работают на уровне текстовой подстановки. Они просто заменяют текст в исходном коде без понимания его структуры. Это может приводить к ошибкам, так называемым "подводным камням" макросов, поскольку макросы могут непреднамеренно изменять код или захватывать локальные переменные.
Макросы в Rust: В Rust макросы более продвинуты, так как они работают с уже разобранными токенами программы или с абстрактным синтаксическим деревом (AST). Это позволяет макросам понимать структуру кода, что делает их более безопасными и предсказуемыми.
Гигиеничные макросы: Одним из преимуществ макросов Rust является их гигиеничность. Это означает, что макросы не могут случайно ссылаться на локальные переменные из окружающего кода, предотвращая тем самым многие типичные ошибки, связанные с макросами.
|
|
|
Основным недостатком использования макроса является его влияние на читаемость и удобство обслуживания кода.
В разделе «Декларативные макросы» объяснялось, что макросы позволяют создавать DSL для краткого выражения ключевых особенностей вашего кода и данных. Однако это означает, что любой, кто читает или поддерживает код, теперь должен понимать этот DSL — и его реализацию в определениях макросов — в дополнение к пониманию Rust.
Чтобы уменьшить влияние на читаемость кода с наличием макросов:
1. Избегайте макрорасширений, вставляющих ссылки.
Избегая автоматической вставки ссылок или других изменений аргументов внутри макросов, вы делаете ваш код более прозрачным, гибким и предсказуемым. Пользователи макросов получают полный контроль над тем, как они хотят передавать аргументы, что соответствует общим принципам разработки на Rust и способствует лучшей читаемости и поддерживаемости кода.
т.е. вместо:
macro_rules! my_macro {
($x:expr) => {
process(& $x)
};
}
Предоставить явную возможность передачи по ссылки:
macro_rules! my_macro {
($x:expr) => {
process($x)
};
}
2. Старайтесь избегать нелокальных операций управления потоком в макросах
Это операции, которые изменяют порядок выполнения кода за пределами текущего блока или макроса.
Примеры таких операций включают:
- return: Возврат из функции.
- break: Прерывание цикла или блока.
- continue: Переход к следующей итерации цикла.
- panic!: Прерывание выполнения программы с ошибкой.
Эти операции влияют на управление потоком выполнения не только внутри макроса, но и на окружающий его код.
|
|
📌 Тестирование макросов
testing-proc-macros
|
|
|
Гигиена локальных переменных в макросах
Создание переменные в макросах нарушает гигиену
syntax-extensions/hygiene
|
Эта программа печатает 14
Гигиена относится только к локальных переменных в макросах, но не к константным, поэтому локальная x не учитывается и ее перекрывает x из внешней области.
А константа K берется ближняя.
При раскрытии макроса переменная K const перекрывается так как гигиена только к локальным переменным
fn main() {
let x: u8 = 1;
const K: u8 = 2;
macro_rules! m {
() => {
print!("{}{}", x, K);
};
}
{
let x: u8 = 3;
const K: u8 = 4;
m!(); // 14
}
}
|
|
|
Стандартные макросы
- println! - Макрос вывода в
io::stdout с переносом строки
- print! - Макрос вывода в
io::stdout
- eprint! - Макрос вывода в
io::stderr
- eprintln! - Макрос вывода в
io::stderr с переносом строки
- assert_eq! - Макрос сравнивает два значения и вызывает
panic!() при не равенстве их
- assert! - Макрос вызывает panic! если передать false
- assert_ne - Макрос не равенства
- unreachable! - Макрос код который не должен исполнится
- unimplemented! - Плейсхолдер для ещё не написанного кода
- todo! - Указывает на незавершенный код. Приказ компилятору "молчать"
- env! - Проверяет переменную среды во время компиляции
- option_env! - Проверяет переменную среды во время компиляции
- file! - возвращает имя файла в котором вызван
- line! - возвращает номер строки в котором вызван
- module_path! - путь к модулю в котором вызван
- include! - подключает файл в код после build.rs сценария
- include_str! - подключает файл в документацию
- include_bytes! - создает
&'static [u8; N] из файла байт
- include_str! - создает
&'static str из файла байт
- std::concat! макрос объединяет литералы в &str
- std::stringify! макрос создает &str из всех токенов
- thread_local - локальная копия объекта для статических переменных
- write! - Запишите отформатированные данные в буфер
- writeln! - Запишите отформатированные данные в буфер с добавлением новой строки
|
|
|
fn main(){
// Макрос вывода в `stdout` с переносом на новую строку
println!("Значение x равно {} и значение y равно {}", x, y);
eprintln!("Error: Could not complete task"); // output to io::stderr
// Макрос вывода в stdout без переноса строки
print!("Значение x равно {}", x);
eprint!("Error: Could not complete task");// output to io::stderr
// Макрос вызывает остановку текущего потока исполнения с заданным сообщением.
panic!("msg")
// Макрос сравнивает два значения и вызывает panic!() при не равенстве их
assert_eq!(1,1)
// Макрос вызывает panic! если передать false
assert!(false);
// Макрос не равенства
assert_ne!(2, 3);
assert_ne!(2, 3, "we are testing that the values are not equal");
// Макрос код который не должен исполнится
unreachable!()
// Плейсхолдер для ещё не написанного кода
unimplemented!()
todo!();
// Проверяет переменную среды во время компиляции
let path: &'static str = env!("PATH");
let key: Option<&'static str> = option_env!("SECRET_KEY");
// Макрос возвращает имя файла в котором вызван
std::file!()
}
|
|
|
Макрос try! используется для обработки ошибок.
Он принимает нечто возвращающее Result<T, E> и возвращает T если было возвращено Ok<T> иначе он делает возврат из функции со значением Err(E)
use std::fs::File;
fn foo() -> std::io::Result<()> {
let f = File::create("foo.txt");
let f = match f {
Ok(t) => t,
Err(e) => return Err(e),
};
Ok(())
}
|
|
Стандартные макросы
unimplemented!() плейсхолдер для ещё не написанного кода
|
Семейство макросов, возвращающих — !
panic!("something went wrong") — для сигнализации о багах
**unimplemented!() **— плейсхолдер для ещё не написанного кода
if complex_condition {
complex_logic
} else {
unimplemented!()
}
unreachable!() — маркер для "невозможных" условий. Сказать компилятору что этот блок не будет выполнятся к примеру в match по диапазону если знаем что не весь диапазон используется
|
|
|
Термин макрос относится к семейству функций в Rust: декларативные макросы macro_rules!
и три вида процедурных макросов:
- Пользовательские
#[derive] макросы, которые определяют код, добавленный с derive атрибутом, используемым в структурах и перечислениях
- Макросы, подобные атрибутам, которые определяют настраиваемые атрибуты, применимые к любому элементу.
- Макросы, похожие на функции, которые выглядят как вызовы функций, но работают с токенами, указанными в качестве их аргумента.
|
|
Для лучшего понимания конструкции, концепций, использования и функций процедурных макросов прочитайте следующие статьи:
Библиотеки:
|
В экосистеме Rust есть несколько хорошо известных крейтов, которые почти всегда используются для реализации процедурных макросов:
crate syn представляет собой реализацию AST в Rust.
crate quote обеспечивает квазицитирование, которое позволяет превращать структуры данных синтаксического дерева Rust в токены исходного кода эргономичным и удобочитаемым способом.
crate proc-macro2 предоставляет унифицированный proc_macro API для всех версий компилятора Rust и делает процедурные макросы пригодными для модульного тестирования.
В настоящее время они являются основой для написания реализации процедурных макросов. Несмотря на это, разработчики в основном склонны не использовать его syn в тривиальных случаях (не требующих большого анализа AST), поскольку это существенно сокращает время компиляции , или предпочитают использовать более простые и менее мощные ящики для анализа AST (например venial,).
Кроме того, можно использовать больше экосистемных ящиков, чтобы иметь меньше шаблонов, лучшую эргономику и «батарейки в комплекте».
Наиболее примечательными среди них являются:
- darling, что делает анализ декларативных атрибутов более простым и эргономичным.
- synstructure, предоставляя вспомогательные типы для сопоставления с вариантами перечисления и извлекая привязки к каждому из полей в производной структуре или перечислении общим способом.
- synthez, предоставляющий производные макросы для анализа AST (да, производные макросы для производных макросов!) и другие полезные «батарейки» для повседневной рутины написания процедурных макросов.
|
|
Процедурные макросы
1. Custom #[derive] макросы
|
Эти макросы позволяют автоматически реализовывать трейты для структур и перечислений.
Стандартные трейты, такие как Debug, Clone и Serialize, часто реализуются с помощью #[derive].
Пользователи могут создавать свои собственные derive макросы для автоматической генерации кода.
-
Первое заключается в том, что derive макросы добавляются к входным токенам, а не заменяют их полностью.
Это означает, что определение структуры данных остается нетронутым, но макрос имеет возможность добавлять связанный код.
-
Во-вторых, макрос derive может объявлять связанные вспомогательные атрибуты, которые затем могут использоваться для маркировки частей структуры данных, требующих специальной обработки.
Например, crate serde предоставляет макрос derive с вспомогательными атрибутами, такими как Serialize и Deserialize
#[derive(Debug, Deserialize)]
struct MyData {
#[serde(default = "generate_value")]
value: String,
...
- Последний аспект derive макросов, о котором следует знать, заключается в том, что crate syn может взять на себя большую часть тяжелой работы, связанной с разбором входных токенов в эквивалентные узлы в AST
#[derive(Debug, Clone)]
struct Point {
x: f64,
y: f64,
}
//или создать собственный
// В crate `hello_derive`
use proc_macro::TokenStream;
use quote::quote;
use syn;
#[proc_macro_derive(Hello)]
pub fn hello_derive(input: TokenStream) -> TokenStream {
// Разбор входного токена
let ast = syn::parse(input).unwrap();
// Генерация кода
impl_hello(&ast)
}
fn impl_hello(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let gen = quote! {
impl Hello for #name {
fn hello() {
println!("Hello from {}", stringify!(#name));
}
}
};
gen.into()
}
// использование в основном проекте
use hello_derive::Hello;
trait Hello {
fn hello();
}
#[derive(Hello)]
struct MyStruct;
fn main() {
MyStruct::hello(); // Выведет: Hello from MyStruct
}
|
|
Процедурные макросы
2. Атрибутные макросы (Attribute-like macros)
Атрибутные макросы позволяют добавлять произвольные атрибуты к элементам кода, таким как функции, модули или структуры. Они обрабатывают элемент, к которому применен атрибут, и могут изменять его или генерировать дополнительный код.
|
// В crate `uppercase_attr`
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn};
#[proc_macro_attribute]
pub fn uppercase(_attr: TokenStream, item: TokenStream) -> TokenStream {
// Разбор входного токена как функции
let input = parse_macro_input!(item as ItemFn);
let name = &input.sig.ident;
let block = &input.block;
let attrs = &input.attrs;
let vis = &input.vis;
let sig = &input.sig;
// Генерация новой функции с преобразованием возвращаемой строки в верхний регистр
let expanded = quote! {
#(#attrs)*
#vis #sig {
let result = (|| #block)();
println!("Function {} returned: {}", stringify!(#name), result.to_uppercase());
result.to_uppercase()
}
};
TokenStream::from(expanded)
}
// использование в основном проекте
use uppercase_attr::uppercase;
#[uppercase]
fn greet() -> String {
"Hello, Rust!".to_string()
}
fn main() {
let message = greet();
println!("Final message: {}", message); // Function greet returned: HELLO, RUST!
}
|
|
Процедурные макросы
3. Функциональные макросы (Function-like macros)
Функциональные макросы похожи на обычные функции, но они работают на уровне синтаксического анализа. Они принимают произвольный набор токенов и возвращают новые токены, которые затем вставляются в исходный код.
|
// В crate `repeat_macro`
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitInt};
#[proc_macro]
pub fn repeat(input: TokenStream) -> TokenStream {
// Разбор входного токена как целого числа
let count = parse_macro_input!(input as LitInt).base10_parse::().unwrap();
// Генерация повторяющегося кода
let expanded = quote! {
{
for _ in 0..#count {
println!("Repeated!");
}
}
};
expanded.into()
}
// использование в основном проекте
use repeat_macro::repeat;
fn main() {
repeat!(3);
// Выведет:
// Repeated!
// Repeated!
// Repeated!
}
|
|
Процедурные макросы
procedural macros действуют как функции (и являются типом процедуры)
|
Процедурные макросы принимают некоторый код в качестве входных данных, работают над этим кодом и создают некоторый код в качестве вывода, а не выполняют сопоставления с шаблонами и замену кода другим кодом, как это делают декларативные макросы.
Процедурные макросы представляют собой гораздо более мощный инструмент генерации кода.
Они называются процедурными , потому что реализация макросов представляет собой обычный код Rust, который работает напрямую с AST преобразованного кода (вы пишете процедуры, которые преобразуют ваш код).
Для процедурного макроса требуется отдельный crate proc-macro = true
Процедурные макросы негигиеничны , поэтому при их внедрении нужно быть осторожным, чтобы гарантировать, что макрос работает в максимально возможном количестве контекстов
На данный момент в Rust есть три вида процедурных макросов:
proc_macro функционально-подобные макросы, использование которых похоже на использование обычных декларативных макросов, но они принимают произвольные токены на ввод (а декларативные - нет) и в целом более мощные (могут содержать сложную логику для генерации простого кода):
Cargo.toml:
[lib]
proc-macro = true
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro]
pub fn make_answer(_: TokenStream) -> TokenStream {
"fn answer() -> u32 { 42 }".parse().unwrap()
}
А затем мы используем его в двоичном контейнере для вывода «42» на стандартный вывод.
extern crate proc_macro_examples;
use proc_macro_examples::make_answer;
make_answer!();
fn main() {
println!("{}", answer());
}
proc_macro_attribute макросы атрибутов , позволяющие создавать собственные атрибуты Rust :
Идиоматично proc_macro_attribute следует использовать для генерации произвольных функций
#[proc_macro_attribute]
pub fn route(attr: TokenStream, item: TokenStream) -> TokenStream {
// code...
}
#[route(GET, "/")]
fn index() {}
proc_macro_derive производные макросы , которые позволяют предоставлять настраиваемые реализации для атрибута #[derive(Trait)]:
Идиоматично, proc_macro_derive следует использовать только для получения реализаций признаков
#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_: TokenStream) -> TokenStream {
"impl Struct{ fn answer() -> u32 { 42 } }".parse().unwrap()
}
#[derive(AnswerFn)]
struct Struct;
``
</div></td>
<td id="macro_097d671ec71e3d57_other"><div class="cell-content" contenteditable="true"></div></td>
</tr>
<tr id="macro_0a0bbda725a131a9">
<td id="macro_0a0bbda725a131a9_topic"><div class="cell-content" contenteditable="true">
#### Декларативные макросы
Синтаксис `macro_rules!`
* [wiki/Hygienic_macro](https://en.wikipedia.org/wiki/Hygienic_macro)
* [Макросы для общего метапрограммирования](https://doc.rust-lang.org/book/ch19-06-macros.html#declarative-macros-with-macro_rules-for-general-metaprogramming)
* [rust-by-example/macros](https://doc.rust-lang.org/rust-by-example/macros.html)
* [Маленькая книга макросов Rust](https://danielkeep.github.io/tlborm/book/README.html)
* [reference/macros-by-example](https://doc.rust-lang.org/reference/macros-by-example.html)
Декларативные макросы хороши тем, что они гигиеничны.
</div></td>
<td id="macro_0a0bbda725a131a9_content"><div class="cell-content" contenteditable="true">
macro_rules! $name {
($matcher) => {$expansion};
($matcher) => {$expansion};
// …
($matcher) => {$expansion}
}
Вы можете использовать квадратные скобки ([]), круглые скобки (()) или фигурные скобки ({})
---
Макросы обладают гигиеничностью т.е. не захватывают локальные переменные (глобальные могут), но мы можем передать в макрос ресурс (не перемещая данные)
```rust
macro_rules! inc_item {
{ $x:ident } => { $x.contents += 1; }
}
#[derive(Debug)]
struct Item{
contents: i32
}
fn main() {
let mut x = Item { contents: 42 }; // type is not `Copy`
// Item не перемещается (moved), так как макрос встраивает (раскрывается на месте) код
// x.contents += 1;
// Расширенный код включает в себя изменение на месте, через владельца элемента, а не ссылку.
inc_item!(x);
println!("x is {x:?}");// 43
}
|
|
Декларативные макросы macro_rules!
effective-rust/macros
|
Если вам нужно генерировать код для каждого поля структуры или каждого варианта перечисления, предпочтительнее использовать derive макрос, а не процедурный макрос, который генерирует тип — это более идиоматично, простота и читаемость,
интеграция с инструментами (IDE и утилиты для генерации документации),
гигиеничность и безопасность, меньше кода для поддержки в сравнении с процедурными макросами
macro_rules! create_function {
($func_name:ident) => {
fn $func_name() {
println!("Function {:?} is called", stringify!($func_name));
}
};
}
// Создание функции `foo`
create_function!(foo);
fn main(){
foo();
}
|
|
|
// Этот простой макрос называется `say_hello`.
macro_rules! say_hello {
// `()` указывает, что макрос не принимает аргументов.
() => (
// Макрос будет раскрываться с содержимым этого блока.
println!("Hello!");
)
}
fn main(){
// Этот вызов будет раскрыт в код `println!("Hello");`
say_hello!();
}
|
|
|
Они называются декларативными, потому что реализация макроса представляет собой объявление правил преобразования кода (вы объявляете, как ваш код будет преобразован)
macro_rules! vec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
/*
Который генерирует такой код:
{
let mut temp_vec = Vec::new();
temp_vec.push(1);
temp_vec.push(2);
temp_vec.push(3);
temp_vec
}
*/
// Вызов макроса:
fn main(){
let v = vec![1, 2, 3];
}
Декларативные макросы используются не только для целей генерации кода.
Довольно часто они также используются для создания абстракций и API, потому что все они реализуют гораздо более эргономичные функции, чем обычные функции: именованные аргументы, вариативность и т. д.
|
|
пример создание переменной декларативным макросом
|
macro_rules! foo {
($v:ident) => (let $v = 3; );
}
fn main() {
foo!(x);
println!("{}", x);
}
macro_rules! foo {
() => (fn x() {print!("ffff") });
}
fn main() {
foo!();
x();
}
|
|
Реализация макроса вектора
|
// Образец, окруженный $(...),*, будет соответствовать нулю или более выражениям, разделенным запятыми.
macro_rules! myvec {
( $( $x:expr ),* ) => {
{
let mut temp_vec = Vec::new();
$(
temp_vec.push($x);
)*
temp_vec
}
};
}
fn main(){
let vector = myvec![1,2,3]; // или let vector = myvec!(1,2,3);
for (idx,val) in vector.iter().enumerate(){
println!("vector[{}]={}", idx,*val);
}
}
Где синтаксис:
$(
temp_vec.push($x);
)*
Означает соединить кусочки раздробленного синтаксиса, захваченные при сопоставлении с соответствующим образцом.
Каждое соответствующее выражение $x будет генерировать одиночный оператор push в развернутой форме макроса.
Повторение в развернутой форме происходит синхронно с повторением в форме образца
|
|
|
Пример matching
// Правая часть может быть (),{},[]
macro_rules! foo {
(x => $e:expr) => (println!("mode X: {}", $e));
(y => $e:expr) => {println!("mode Y: {}", $e)};
(z => $e:expr) => [println!("mode Z: {}", $e)];
}
fn main(){
foo!(y => 3); // mode Y: 3
}
|
|
|
|
|
|
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() {
use std::fmt::Write;
let mut out = String::new();
write_html!(&mut out,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
]);
assert_eq!(out,
`"<html><head><title>Macros guide</title></head>\
<body><h1>Macros are the best!</h1></body></html>"`);
}
|
|
|
use std::fmt::Write as fmt_Write;
use std::io::Write;
macro_rules! write_html {
($w:expr, ) => (());// распознаватель 1
($w:expr, $e:tt) => (write!($w, "{}", $e));// распознаватель 2
($w:expr, $tag:ident [ $($inner:tt)* ] $($rest:tt)* ) => {{
write!($w, "\n<{}>", stringify!($tag));
write_html!($w, $($inner)*); // рекурсивно вложенные [...] обработать
write!($w, "{}>\n", stringify!($tag));
write_html!($w, $($rest)*);// следующий элемент после [...]
}};
}
fn main(){
let mut buf = String::new();
//write_html!(&mut buf,"Hello");// распознаватель 2
write_html!(&mut buf,
html[
head[title["Macros guide"]]
body[h1["Macros are the best!"]]
] www[http["url"]]);
println!("{buf}");
}
|
|
Реализуйте btreemap! макрос, позволяющий создавать BTreeMap эргономично и декларативно (аналогично vec!).
Предоставьте две реализации: одну с помощью декларативного макроса, а другую с помощью процедурного макроса.
|
С помощью декларативного макроса
extern crate step_3_2;
use step_3_2::btreemap_proc;
macro_rules! btreemap_decl {
( $(( $key:expr, $val:expr )),* $(,)?) => {{
let mut map = std::collections::BTreeMap::new();
$(
map.insert($key, $val);
)*
map
}};
}
fn main() {
let map_decl = btreemap_decl![
(1, "one"),
(2, "two"),
(3, "three"),
(4, "four"),
(5, "five"),
];
let map_proc = btreemap_proc![
(1, "one"),
(2, "two"),
(3, "three"),
(4, "four"),
(5, "five"),
];
dbg!(&map_proc);
assert_eq!(map_decl, map_proc);
}
С помощью процедурного макроса
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseBuffer, Result};
use syn::punctuated::Punctuated;
use syn::{parenthesized, parse_macro_input, Expr};
/// Parses `(Expr, Expr)`
struct Pair {
first: Expr,
second: Expr,
}
impl Parse for Pair {
fn parse<'a>(input: &'a ParseBuffer<'a>) -> Result<Self> {
let content;
parenthesized!(content in input);
let first = content.parse()?;
content.parse::<syn::Token![,]>()?;
let second = content.parse()?;
Ok(Pair { first, second })
}
}
/// Parses `(Expr, Expr),*`
struct Pairs {
pairs: Punctuated<Pair, syn::Token![,]>,
}
impl Parse for Pairs {
fn parse<'a>(input: &'a ParseBuffer<'a>) -> Result<Self> {
Ok(Pairs {
pairs: input.parse_terminated(Pair::parse)?,
})
}
}
#[proc_macro]
pub fn btreemap_proc(input: TokenStream) -> TokenStream {
let Pairs { pairs } = parse_macro_input!(input as Pairs);
let inserts = pairs
.into_iter()
.map(|Pair { first, second }| quote! { map.insert(#first, #second); })
.collect::<Vec<_>>();
let res = quote! {{
let mut map = std::collections::BTreeMap::new();
#(#inserts)*
map
}};
res.into()
}
fn main(){}
|
|
|
- Patterns and Designators (Указатели)
- Overloading (Перегрузка)
- Repetition (Повторение)
|
|
|
Аргументы макроса имеют префикс знака доллара $ и тип аннотируется с помощью указателей фрагмента
|
|
|
block Блок операторов или выражений, например, {}{ let x = 5; }
expr Выражение, например, x, , или 1 + 1, String::new(), vec![]
ident используют для обозначения имени переменной/функции
item Элемент, например функция, структура, модуль и т. д. fn foo() {}, struct Bar;, use std::io;
pat (образец) Шаблон, например , или Some(t)(17, 'a')_ , c @ 'a' ... 'z', (true, &x)
path (квалифицированное имя) std::collection::HashSet, Vec::new
stmt (единственный оператор) Заявление, например , или let x = 1 + 1; , String::new();, vec![];
tt (единственное дерево лексем) + , foo , 5 , [?!(???)]
ty (тип) , например String, usize или Vec<u8>, i32, &T, Vec<(char, String)>
$(x),+ повторить один или более раз
$(x),? повторить ноль или один раз
$(x),* повторить ноль или более раз
$x:vis Модификатор видимости; pub, и т. д. pub(crate)
ident: Идентификатор, например в идентификаторе let x = 0;, x
path: Путь. Например: T::SpecialA или ::std::mem::replacetransmute::<_, int>
expr: выражение. Например: 2 + 2;, if true then { 1 } else { 2 };, f(42)
ty: тип. Например: i32;, Vec<(char, String)>;, &T
pat: образец. Например: Some(t);, (17, 'a');, _
stmt: единственный оператор. Например: let x = 3
block: последовательность операторов, ограниченная фигурными скобками. Например: { log(error, "hi"); return 12; }
item: элемент. Например: fn foo() { };, struct Bar;
meta: «мета-элемент», как в атрибутах. Например: cfg(target_os = "windows")
вещи, которые входят внутрь, #[...] и #![...] атрибуты, cfg!(windows), doc="comment"
tt: единственное дерево лексем.
$x:lifetime Время жизни (например 'a, 'static и т. д.).
$x:literal Буквальное (например 3, "foo", b"bar","Hello World!", 3.14, '🦀' и т.д.).
Есть дополнительные правила относительно лексем, следующих за метапеременной:
- **за expr должно быть что-то из этого (
, ;)
- **за ty и path должно быть что-то из этого (
, : = > as)
- **за pat должно быть что-то из этого (
, =)
- **за другими лексемами могут следовать любые символы.
|
|
|
// Этот простой макрос называется `say_hello`.
macro_rules! say_hello {
// () аналог match
// `()` указывает, что макрос не принимает аргументов.
() => (println!("Hello!"); );
// с аргументом x
(x => $e:expr) => (
// Макрос будет раскрываться с содержимым этого блока.
println!("с аргументом x {:?}!",$e);
);
($e:expr) => (
// Макрос будет раскрываться с содержимым этого блока.
println!("Вывод выражения {:?}!",$e);
println!("Выражение как есть {:?}!",stringify!($e));
);
}
macro_rules! create_function {
// Этот макрос принимает аргумент идентификатора `ident` и
// создаёт функцию с именем `$func_name`.
// Идентификатор `ident` используют для обозначения имени переменной/функции.
($func_name:ident) => (
fn $func_name() {
// Макрос `stringify!` преобразует `ident` в строку.
println!("Вызвана функция {:?}()", stringify!($func_name))
}
)
}
fn main() {
// Этот вызов будет раскрыт в код `println!("Hello");`
say_hello!();
say_hello!(x => "c x arguments");
say_hello!("без именной переменной");
say_hello!({[1,2] });
// Создадим функции с именами `foo` и `bar` используя макрос, указанный выше.
create_function!(foo);
foo();
}
|
|
Некоторым спецификаторам фрагментов требуется токен, следующий за ним, который должен быть одним из ограниченного набора, называемым «follow set»
|
| meta | token |
| expr , stmt | , ; |
| ty , path | `, = |
| pat | `, = |
| ident , block , item , meta , tt | любой токен |
|
|
|
macro_rules! name_macro{
// первый match ------------------------------------------------
// использование: name_macro!();
() => ( println!("Нет аргументов!"); );
// второй match -----------------------------------------------
// expr - любое выражение
// использование: name_macro!(x => vec![1,2,3]); Аргумент x = вектор
(x => $e:expr) => (
// Макрос будет раскрываться с содержимым этого блока.
println!("Выражение с аргументом x {:?}!",$e);
);
// третий match -----------------------------------------------
// использование: name_macro!(vec![1,2,3]); Аргумент $e = вектор
// использование: name_macro!({let a = 1+1; a}); Аргумент $e = 2
($e:expr) => (
println!("Выражение {:?}!",$e);
);
// четвертый match ---------------------------------------------
// * или + множество выражений
// использование: name_macro!(1,2,3);
( $($e:expr),* ) => (
// $( )* аналог цикла, $e в каждой итерации новый
$(
println!("Выражение {:?}!",$e);
)*
);
// пятый match -------------------------------------------------
// ident - для обозначения имени переменной/функции
// ty - для типа данных
// _ как разделитель вместо запятой из-за конфликта с предыдущим match
// использование: name_macro!(foo,i32); foo(5);
($func_name:ident _ $t:ty) => (
fn $func_name(value:$t) {
// Макрос `stringify!` преобразует `ident` в строку.
println!("Вызвана функция {:?}() {:?}", stringify!($func_name),value+2);
}
);
// шестой match -------------------------------------------------
// item - элемент
// использование: name_macro!(test, fn test(v:i32)->i32{ v } ,4);
($func_name:ident, $func_call:item, $e:expr) => (
$func_call;// создать
println!("Функция вернула {}",$func_name($e));
);
// седьмой match -------------------------------------------------
// pat - образец match
// использование: name_macro!( m => Some(1...10),Some(3) );
(m => $value:pat,$e:expr) => (
match $e {
v @ $value => println!("Значение {:?}",v),
_ => println!("Нет совпадения")
}
);
// восьмой match -------------------------------------------------
// block - блок кода
// использование:
// fn show(){ println!("call show"); }
// name_macro!( b => {show();}, show);
(b => $code:block,$func_name:ident) => (
$code;// call show
$func_name(); // call show
);
// девятый match -------------------------------------------------
// stmt - единственный оператор
// использование:
// let mut x = 3;
// name_macro!( s => x = 4,x);
(s => $code:stmt,$e:expr ) => (
$code;// x = 4;
$e = $e + 1;
println!("Значение x={:?}",$e); // 5
);
}
fn main(){}
|
|
|
macro_rules! foo4 {
($w:ident) => {//вызов с идентификатом
let $w = 3;
$w
};
// one=> просто для разделения вызова на разные реализация использования выражения
(one=>$w:expr) => {//вызов выражением
$w+10
};
(two=>$w:expr) => {//вызов выражением
$w(1)
};
($w:path) => {//вызов квалифицированным именем A::method
$w(1)
};
($w:ty,$val:ident,$data:expr) => {// $ty вызов типом
{
let $val:$w = $data(44);
$val
}
};
(pat => $w:pat) => {// вызов образцом
println!(" pat " );
};
($w:stmt) => {// вызов единственным оператором
println!(" stmt " );
};
($w:block)=>{// https://doc.rust-lang.org/stable/reference/expressions/block-expr.html
//вызов последовательностью операторов, ограниченных фигурными скобками
println!(" block " );
};
($w:item) => {// элемент. Например: fn foo() { }; struct Bar; // https://doc.rust-lang.org/stable/reference/items.html
println!(" item " );
};
// meta: «мета-элемент», как в атрибутах. Например: cfg(target_os = "windows").
($w:meta) => {
println!(" meta " );
};
($w:tt) => {//tt: единственное дерево лексем, подходит просто значение 7
println!(" tt " );
};
}
struct A;
impl A{
pub fn method(p:i32)->i32{p}
}
trait T{}
fn main(){
foo4!(x);//идентификатор ident
assert_eq!(3,x);
assert_eq!(12,foo4!(one=> 1+1));//выражение expr
let b:bool = true;
assert_eq!(11,foo4!(one=> if b { 1 } else { 2 }));//выражение expr
assert_eq!(21,foo4!(two=> |x|->i32 {x+20} ) );//выражение expr
fn test_foo4(p:i32)->i32{
p*2
}
assert_eq!(2,foo4!(two=> test_foo4 ) );//выражение expr
assert_eq!(30,foo4!(one=> test_foo4(10) ) );//выражение expr
assert_eq!(1,foo4!(A::method));//квалифицированное имя path
assert_eq!(1,foo4!(two=>A::method));//выражение expr
assert_eq!(45,foo4!(i32,x,|v:i32|->i32{v+1}));// тип ty
foo4!(pat => A{} );// образец pat
foo4!(pat => (17, 'a') );// образец pat
foo4!(pat => Some(T));// образец pat
foo4!({let i = 9;});// единственный оператор stmt или block
foo4!({let i = 9;let y = 9;});// единственный оператор stmt или block
let a = A{};
foo4!(a);// единственный оператор stmt
//foo2!(x);
//println!("{}", x);
//foo3!(my_fn);
//assert_eq!(20,my_fn(2));
}
|
|
$i:ident: Этот указатель сопоставляет с идентификаторами (именами переменных или функций), такими как foo и bar.
$e:expr: Этот указатель сопоставляет с выражениями, такими как 5
|
macro_rules! example {
($(I $i:ident)* E $e:expr) => { ($($i)-*) * $e };
}
fn main(){
let foo = 2;
let bar = 3;
// The following expands to `(foo - bar) * 5`
example!(I foo I bar E 5);
}
|
|
$x:ident метка контекста введенной переменной их программы
|
//Переменная обладает меткой контекста синтаксиса, где она была введена.
// $x:ident
// Это справедливо для let привязок и меток loop, но не для элементов.
macro_rules! foo1 {
() => (let x = 3);
}
//foo1!();
// передавать имя переменной при вызове, тогда она будет обладать меткой правильного контекста синтаксиса.
macro_rules! foo2 {
($v:ident) => (let $v = 3);
}
//fn main(){
// foo2!(x); ввод переменной
// println!("{}", x); }
macro_rules! foo3 {
($v:ident) => ( let $v = |p:i32|->i32{p*10};);
}
// foo3!(my_fn);
// assert_eq!(20,my_fn(2));
// Для элементов не работает !
macro_rules! foo4 {
() => (fn x() { });
}
|
|
|
tt будет соответствовать любому отдельному токену или любой паре скобок / скобок / фигурных скобок с их содержимым
Например, для следующей программы:
fn main() {
println!("Hello world!");
}
Деревья токенов будут:
# fn
# main
# ()
# ∅
# { println!("Hello world!"); }
# println
# !
# ("Hello world!")
# "Hello world!"
# ;
Каждый из них образует дерево , где простые маркеры ( fn, и main т.д.) являются листья, и все в окружении (), [] или {} имеет поддерево.
Обратите внимание, что "(" он не отображается один в дереве токенов: ( сопоставление невозможно без сопоставления с соответствующим )
Например:
macro_rules! {
(fn $name:ident $params:tt $body:tt) => { /* … */ }
}
будет соответствовать описанной выше функции с
$name → main
$params → ()
$body → { println!("Hello world!"); }
Дерево токенов - наименее требовательный тип метапеременных: оно соответствует чему угодно.
Он часто используется в макросах, в которых есть часть «на самом деле все равно», и особенно в макросах, в которых есть «голова» и «хвост».
Например, у println! макроса есть соответствие ветвления, ($fmt:expr, $($arg:tt)*) где $fmt- строка формата, что $($arg:tt)* означает «все остальное» и просто перенаправляется format_args!. Это означает, что вам println! не нужно знать фактический формат и выполнять сложное сопоставление с ним.
|
|
|
|
|
|
// `test!` будет сравнивать `$left` и `$right`
// по разному, в зависимости от того, как вы объявите их:
macro_rules! test {
// Не нужно разделять аргументы запятой.
// Можно использовать любой шаблон!
($left:expr; and $right:expr) => (
$left && $right
);
// ^ каждый блок должен заканчиваться точкой с запятой.
($left:expr; or $right:expr) => (
$left || $right
);
}
fn main(){
if test!(1i32 + 1 == 2i32; and 2i32 * 2 == 4i32){
println!("yes");
}else{
println!("no");
}
if test!(true; or false){
println!("yes");
}else{
println!("no");
}
}
|
|
|
- знак
+ чтобы указать, какие аргументы могут повторяться хоть один раз
- знак
* чтобы указать, какие аргументы могут повторяться ноль или несколько раз
|
|
общий вид
$ ( ... ) sep rep
sep - необязательный токеном-разделителем.
Это не может быть разделитель или один из операторов повторения.
Распространенными примерами являются , и ;
rep - обязательный оператором повторения.
В настоящее время это может быть:
?: указывает не более одного повторения
*: указание на ноль или более повторений
+: указывает на одно или несколько повторений
|
macro_rules! vec_strs {
(
// Start a repetition:
$(
// Each repeat must contain an expression...
$element:expr
)
// ...separated by commas...
,
// ...zero or more times.
*
) => {
// Enclose the expansion in a block so that we can use
// multiple statements.
{
let mut v = Vec::new();
// Start a repetition:
$(
// Each repeat will contain the following statement, with
// $element replaced with the corresponding expression.
v.push(format!("{}", $element));
)*
v
}
};
}
fn main() {
let s = vec_strs![1, "a", true, 3.14159f32];
assert_eq!(s, &["1", "a", "true", "3.14159"]);
}
|
|
|
// `min!` посчитает минимальное число аргументов.
macro_rules! find_min {
// Простой вариант:
($x:expr) => ($x);
// `$x` следует хотя бы одному `$y,`
($x:expr, $($y:expr),+) => (
//Рекурсивно вызовем `find_min!` на конце `$y`
std::cmp::min($x, find_min!($($y),+))
)
}
fn main(){
println!("{}", find_min!(1u32));
println!("{}", find_min!(1u32 + 2 , 2u32));
println!("{}", find_min!(5u32, 2u32 * 3, 4u32));
}
|
|
Пример цикла
$(...)* - ноль или более раз
$(...)+ - один или более раз
|
macro_rules! test_for{
($($x:expr),*) => {
// цикл
// $(...)* - ноль или более раз
// $(...)+ - один или более раз
$(
println!("{:?}",$x);
)*
}
}
fn main(){
test_for!(1,2,3,4);
}
|
|
Пример вложенного цикла ($y будет дублироваться количество $x раз)
|
// для o_O!(10; [1, 2, 3]; 20; [4, 5, 6]); (число;массив; и т.д.)
macro_rules! o_O {
( $(
$x:expr; [ $( $y:expr ),* ]
);*
) => {
// бегать по общему количеству раз повторов число;массив; это внешний круг
// и внутренний по массиву $( $y:expr ),*
&[ $($( $x + $y ),*),* ]
}
}
macro_rules! o_O {
(
// для [N;[M,...];] ...
$(
$x:expr; [ $( $y:expr ),* ]
);*
) => {
&[ $($( $x + $y ),*),* ]
}
}
fn main(){
let a: &[i32] = o_O!(10; [1, 2, 3]; 20; [4, 5, 6]);
assert_eq!(a, [11, 12, 13, 24, 25, 26])
}
|
|
Работа в замыкании
Компоновка семантики вызова
Упаковка цикл одним вызовом
|
macro_rules! foo{
(valiant_a => $val:ident,$stmt:stmt) => {{
let $val = $val.clone();
$stmt // тут в переданном методе отрабатывает $val прошедший через Clone !
}};
(valiant_b => $val:ident,$router:ident.$method:ident) => {{
// необычная компановка вызова
let $val = $val.clone();
$router.$method(&$val)
}};
}
// В один вызов оформил
macro_rules! no_repeat{
($message:expr, [$( $val:expr,$router:ident.$method:ident, )+] ) => {{
println!("Your message:{}",$message);
let mut i =0;
$(
let res = $router.$method(&$val.clone());
println!("index:{}, res:{}",{i+=1;i},res);
)+
}};
}
struct B(pub i32);
impl Clone for B {
fn clone(&self) -> B {
B(self.0 + 28)
}
}
struct A;
impl A {
fn test(param:&B)->i32{ param.0 }
fn test_self(&self,param:&B)->i32{ param.0 }
}
fn main(){
let b = B(22);
let result = foo!(valiant_a => b,A::test(&b));
assert_eq!(50,result);
let b = B(22);
let result = foo!(valiant_b => b,A.test_self);
//assert_eq!(50,result);
println!("{:?}",result);
no_repeat!("Hello",[
B(22),A.test_self,
B(0),A.test_self,
B(1),A.test_self,
]
);
}
|
|
|
impl Die {
pub fn new(faces: u8) -> Die {
Die { faces }
}
pub fn d2() -> Die {
Self::new(2)
}
pub fn d4() -> Die {
Self::new(4)
}
pub fn d6() -> Die {
Self::new(6)
}
// Many more functions for other dice
}
fn main(){}
Используем макрос для уменьшения дублирования кода
Cargo.toml:
[dependencies]
paste = "1.0.5"
macro_rules! gen_dice_fn_for {
( $( $x:expr ),* ) => {
paste! {
$(
#[allow(dead_code)]
pub fn [<d$x>]() -> Die {
Self::new($x)
}
)*
}
};
}
impl Die {
pub fn new(faces: u8) -> Die {
Die { faces }
}
gen_dice_fn_for![2, 4, 6, 8, 10, 12, 20, 30, 100];
}
fn main(){}
|
|
|
trait T{
fn foo(&self,v:i32)->Result;
}
struct UserId;
impl T for UserId{
fn foo(&self,v:i32)->Result{
Ok(v*v)
}
}
fn main(){
let u:UserId = UserId;
let u = &u as &T;
assert_eq!(25,u.foo(5).unwrap());
}
Или завернуть impl в макрос
macro_rules! impl_t {
( ($body:expr,$item:ty) ) => {
{
impl T for $item {
fn foo(&self,v:i32)->Result{
$body(v)
}
}
}
}
}
fn main(){
let v = impl_t!(
(
|v:i32|->Result{Ok(v*v)},
UserId
)
);
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|