खोज…
टिप्पणियों
मैक्रो का एक पूर्वाभ्यास द रस्ट प्रोग्रामिंग लैंग्वेज (उर्फ द बुक) में पाया जा सकता है।
ट्यूटोरियल
मैक्रोज़ हमें कई बार दोहराए जाने वाले सिंटैक्टिकल पैटर्न्स की अनुमति देते हैं। उदाहरण के लिए:
/// 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)
रूप में 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!
वाक्य - विन्यास:
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,
}
}
}
बस! हमने अपना पहला मैक्रो बनाया है।
(इसे जंग के खेल के मैदान में आज़माएं)
एक हैशसेट मैक्रो बनाएँ
// 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)
:
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
उम्मीद कर रहा है। जंग विभिन्न प्रकार के टुकड़े निर्दिष्ट करती है, जिससे इनपुट बहुत लचीला हो सकता है।
विनिर्देशक | विवरण | उदाहरण |
---|---|---|
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
को #[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;
सेट का पालन करें
कुछ खंड विनिर्देशकों को टोकन का पालन करने की आवश्यकता होती है, यह एक प्रतिबंधित सेट में से एक होना चाहिए, जिसे "फॉलो सेट" कहा जाता है। यह रस्ट के सिंटैक्स के लिए कुछ लचीलेपन को मौजूदा मैक्रोज़ को तोड़ने के बिना विकसित करने देता है।
विनिर्देशक | सेट का पालन करें |
---|---|
expr , stmt | => , ; |
ty , path | => , = | ; : > [ { where as |
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);
संकलन के दौरान, यह निम्नलिखित के लिए प्रिंट करेगा:
a = 1, बाकी = 2, 3
a = 2, बाकी = 3
आधार = ३
- व्याख्या का विस्तार
संकलक के साथ चलाएं:
rustc -Z unstable-options --pretty expanded filename.rs
यह सभी मैक्रोज़ का विस्तार करेगा और फिर स्टडआउट के लिए विस्तारित परिणाम को प्रिंट करता है, जैसे कि ऊपर शायद आउटपुट होगा:
#![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 } } };
(यह C कंपाइलर्स gcc
और clang
में -E
फ्लैग के समान है।)