Rust में attribute-like और custom derive macros का उपयोग Rust कोड के एक ब्लॉक को लेने और compile time पर इसे किसी तरह से संशोधित करने के लिए किया जाता है, जिसका उपयोग अक्सर कार्यक्षमता (functionality) जोड़ने के लिए किया जाता है।
Rust में attribute-like और custom derive macros को समझने के लिए, हमें सबसे पहले Rust में implementation structs को संक्षेप में समझना होगा।
structs के लिए Implementations: impl
निम्नलिखित struct को समझना आसान होना चाहिए। दिलचस्प बात तब होती है जब हम ऐसे functions बनाते हैं जो किसी विशेष struct पर काम करते हैं। हम ऐसा impl के साथ करते हैं:
struct Person {
name: String,
age: u8,
}
Associated functions और methods को impl ब्लॉक के अंदर structs के लिए लागू (implement) किया जाता है।
Associated functions की तुलना Solidity के उस परिदृश्य से की जा सकती है जहां किसी struct के साथ इंटरैक्ट करने के लिए एक लाइब्रेरी बनाई जाती है। जब हम using lib for MyStruct को परिभाषित करते हैं, तो यह हमें myStruct.associatedFunction() सिंटैक्स का उपयोग करने की अनुमति देता है। यह function को Self कीवर्ड के माध्यम से myStruct तक एक्सेस देता है।
हम Rust Playground का उपयोग करने की सलाह देते हैं, लेकिन अधिक जटिल उदाहरणों के लिए, आपको अपना IDE सेट करना पड़ सकता है।
आइए नीचे एक उदाहरण देखें:
struct Person {
age: u8,
name: String,
}
// Implement a method `new()` for the `Person` struct, allowing initialization of a `Person` instance
impl Person {
// Create a new `Person` with the provided `name` and `age`
fn new(name: String, age: u8) -> Self {
Person { name, age }
}
fn can_drink(&self) -> bool {
if self.age >= 21 as u8 {
return true;
}
return false;
}
fn age_in_one_year(&self) -> u8 {
return &self.age + 1;
}
}
fn main() {
// Usage: Create a new `Person` instance with a name and age
let person = Person::new(String::from("Jesserc"), 19);
// use some impl functions
println!("{:?}", person.can_drink()); // false
println!("{:?}", person.age_in_one_year()); // 20
println!("{:?}", person.name);
}
उपयोग (Usage):
// Usage: Create a new `Person` instance with a name and age
let person = Person::new(String::from("Jesserc"), 19);
// use some impl functions
person.can_drink(); // false
person.age_in_one_year(); // 20
Rust Traits
Rust traits विभिन्न impls के बीच साझा व्यवहार (shared behavior) को लागू करने का एक तरीका है। इन्हें Solidity में एक इंटरफ़ेस या abstract contract की तरह समझें — कोई भी contract जो इंटरफ़ेस का उपयोग करता है, उसे कुछ निश्चित functions को लागू (implement) करना ही होता है।
उदाहरण के लिए, मान लें कि हमारे पास एक ऐसा परिदृश्य है जहां हमें Car और Boat struct को परिभाषित करने की आवश्यकता है। हम एक ऐसा method जोड़ना चाहते हैं जो हमें किलोमीटर प्रति घंटे में उनकी गति प्राप्त करने की अनुमति दे। Rust में, हम एक सिंगल trait का उपयोग करके और दोनों structs के बीच method साझा करके इसे पूरा कर सकते हैं।
इसे नीचे दिखाया गया है:
// Traits are defined with the `trait` keyword followed by their name
trait Speed {
fn get_speed_kph(&self) -> f64;
}
// Car struct
struct Car {
speed_mph: f64,
}
// Boat struct
struct Boat {
speed_knots: f64,
}
// Traits are implemented for a type using the `impl` keyword as shown below
impl Speed for Car {
fn get_speed_kph(&self) -> f64 {
// Convert miles per hour to kilometers per hour
self.speed_mph * 1.60934
}
}
// We also implement the `Speed` trait for `Boat`
impl Speed for Boat {
fn get_speed_kph(&self) -> f64 {
// Convert knots to kilometers per hour
self.speed_knots * 1.852
}
}
fn main() {
// Initialize a `Car` and `Boat` type
let car = Car { speed_mph: 60.0 };
let boat = Boat { speed_knots: 30.0 };
// Get and print the speeds in kilometers per hour
let car_speed_kph = car.get_speed_kph();
let boat_speed_kph = boat.get_speed_kph();
println!("Car Speed: {} km/h", car_speed_kph); // 96.5604 km/h
println!("Boat Speed: {} km/h", boat_speed_kph); // 55.56 km/h
}
macros कैसे structs को संशोधित कर सकते हैं
function-like macros पर हमारे ट्यूटोरियल में, हमने देखा कि कैसे macros बड़े Rust कोड में println!(...) और msg!(...) जैसे कोड का विस्तार (expand) कर सकते हैं। Solana के संदर्भ में जिन अन्य प्रकार के macros की हमें परवाह है, वे attribute-like macro और derive macro हैं। हम Anchor द्वारा बनाए गए स्टार्टर प्रोग्राम में सभी तीन (function-like, attribute-like, और derive) macros देख सकते हैं:

attribute-like macros क्या कर रहे हैं, इसका अंदाजा लगाने के लिए, हम दो macros बनाएंगे: एक struct में fields जोड़ने के लिए और दूसरा जो उन्हें हटा देता है।
उदाहरण 1: attribute-like macro, fields सम्मिलित करना (inserting)
Rust attributes और macros कैसे काम करते हैं, इसकी बेहतर समझ हासिल करने के लिए, हम एक attribute-like macro बनाएंगे जो:
- एक struct लेता है जिसमें
i32प्रकार केfooऔरbarfields नहीं हैं - उन fields को struct में सम्मिलित करता है
double_fooनामक फ़ंक्शन के साथ एकimplबनाता है जोfooमें जो भी integer मान है, उसका दोगुना लौटाता है।
सेटअप (Setup)
सबसे पहले हम एक नया Rust प्रोजेक्ट बनाते हैं:
cargo new macro-demo --lib
cd macro-demo
touch src/main.rs
Cargo.toml फ़ाइल में निम्नलिखित जोड़ें:
[lib]
proc-macro = true
[dependencies]
syn = {version="1.0.57",features=["full","fold"]}
quote = "1.0.8"
मुख्य प्रोग्राम बनाना
निम्नलिखित कोड को src/main.rs में पेस्ट करें। टिप्पणियों (comments) को पढ़ना सुनिश्चित करें:
// src/main.rs
// Import the macro_demo crate and bring all items into scope with the `*` wildcard
// (basically everything in this crate, including our macro in `src/lib.rs`
use macro_demo::*;
// Apply the `foo_bar_attribute` procedural attribute-like macro we created in `src/lib.rs` to `struct MyStruct`
// The procedural macro will generate a new struct definition with specified fields and methods
#[foo_bar_attribute]
struct MyStruct {
baz: i32,
}
fn main() {
// Create a new instance of `MyStruct` using the `default()` method
// This method is provided by the `Default` trait implementation generated by the macro
let demo = MyStruct::default();
// Print the contents of `demo` to the console
// The `Debug` trait implementation generated by the macro allows formatted output with `println!`
println!("struct is {:?}", demo);
// Call the `double_foo()` method on `demo`
// This method is generated by the macro and returns double the value of the `foo` field
let double_foo = demo.double_foo();
// Print the result of calling `double_foo` to the console
println!("double foo: {}", double_foo);
}
कुछ अवलोकन (observations):
- struct
MyStructमेंfoofields नहीं हैं। - फ़ंक्शन
double_fooको ऊपर दिए गए कोड में कहीं भी परिभाषित नहीं किया गया है, यह मान लिया गया है कि यह मौजूद है।
अब आइए attribute-like macro बनाते हैं जो पर्दे के पीछे MyStruct को संशोधित करेगा।
src/lib.rs के कोड को निम्नलिखित कोड से बदलें (टिप्पणियों को पढ़ना सुनिश्चित करें):
// src/lib.rs
// Importing necessary external crates
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemStruct};
// Declaring a procedural attribute-like macro using the `proc_macro_attribute` directive
// This makes the macro usable as an attribute
#[proc_macro_attribute]
// The function `foo_bar_attribute` takes two arguments:
// _metadata: The arguments provided to the macro (if any)
// _input: The TokenStream the macro is applied to
pub fn foo_bar_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
// Parse the input TokenStream into an AST node representing a struct
let input = parse_macro_input!(_input as ItemStruct);
let struct_name = &input.ident; // Get the name of the struct
// Constructing the output TokenStream using the quote! macro
// The quote! macro allows for writing Rust code as if it were a string,
// but with the ability to interpolate values
TokenStream::from(quote! {
// Derive Debug trait for #struct_name to enable formatted output with `println()`
#[derive(Debug)]
// Defining a new struct #struct_name with two fields: foo and bar
struct #struct_name {
foo: i32,
bar: i32,
}
// Implementing the Default trait for #struct_name
// This provides a default() method to create a new instance of #struct_name
impl Default for #struct_name {
// The default method returns a new instance of #struct_name
// with foo set to 10 and bar set to 20
fn default() -> Self {
#struct_name { foo: 10, bar: 20}
}
}
impl #struct_name {
// Defining a method double_foo for #struct_name
// This method returns double the value of foo
fn double_foo(&self) -> i32 {
self.foo * 2
}
}
})
}
अब, अपने macro का परीक्षण करने के लिए हम cargo run src/main.rs के साथ main.rs में कोड चलाते हैं।
हमें यह आउटपुट मिलता है:
struct is MyStruct { foo: 10, bar: 20 }
double foo: 20
उदाहरण 2: एक attribute-like macro, fields हटाना
attribute-like macros के बारे में सोचने का सबसे अच्छा तरीका यह है कि उनके पास struct को संशोधित करने की असीमित शक्ति है। आइए ऊपर दिए गए उदाहरण को दोहराएं, लेकिन इस बार attribute-like macro struct से सभी fields को हटा देगा।
src/lib.rs को निम्नलिखित से बदलें:
// src/lib.rs
// Importing necessary external crates
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemStruct};
#[proc_macro_attribute]
pub fn destroy_attribute(_metadata: TokenStream, _input: TokenStream) -> TokenStream {
let input = parse_macro_input!(_input as ItemStruct);
let struct_name = &input.ident; // Get the name of the struct
TokenStream::from(quote! {
// This returns an empty struct with the same name
#[derive(Debug)]
struct #struct_name {
}
})
}
src/main.rs को निम्नलिखित से बदलें:
use macro_demo::*;
#[destroy_attribute]
struct MyStruct {
baz: i32,
qux: i32,
}
fn main() {
let demo = MyStruct { baz: 3, qux: 4 };
println!("struct is {:?}", demo);
}
जब आप इसे cargo run src/main.rs के साथ संकलित (compile) करने का प्रयास करेंगे तो आपको निम्नलिखित त्रुटि (error) मिलेगी:

यह अजीब लग सकता है, क्योंकि struct में स्पष्ट रूप से वे fields मौजूद हैं। हालाँकि, attribute-like macro ने उन्हें हटा दिया!
#[derive(…)] macro
#[derive(…)] macro, attribute-like macro की तुलना में बहुत कम शक्तिशाली है। हमारे उद्देश्यों के लिए, एक derive macro एक struct को बढ़ाता (augments) है, यह उसे बदलता नहीं है। (यह कोई सटीक परिभाषा नहीं है, लेकिन अभी के लिए पर्याप्त है)।
अन्य चीजों के अलावा, एक derive macro एक impl को struct से जोड़ सकता है।
उदाहरण के लिए, यदि हम निम्नलिखित करने का प्रयास करते हैं:
struct Foo {
bar: i32,
}
pub fn main() {
let foo = Foo { bar: 3 };
println!("{:?}", foo);
}
कोड संकलित (compile) नहीं होगा क्योंकि structs “प्रिंट करने योग्य (printable)” नहीं होते हैं।
उन्हें प्रिंट करने योग्य बनाने के लिए, उन्हें एक fmt फ़ंक्शन के साथ एक impl की आवश्यकता होती है जो struct का एक स्ट्रिंग प्रतिनिधित्व (representation) लौटाता है।
यदि हम इसके बजाय निम्नलिखित करते हैं:
#[derive(Debug)]
struct Foo {
bar: i32,
}
pub fn main() {
let foo = Foo { bar: 3 };
println!("{:?}", foo);
}
हमें इसके प्रिंट होने की उम्मीद है:
Foo { bar: 3 }
derive attribute ने Foo को इस तरह से “बढ़ाया (augmented)” कि println! इसके लिए एक स्ट्रिंग प्रतिनिधित्व बना सका।
सारांश (Summary)
एक impl ऐसे फ़ंक्शंस का एक समूह है जो किसी struct पर काम करते हैं। वे struct के समान नाम का उपयोग करके struct से “जुड़े” होते हैं। एक trait यह सुनिश्चित करता है कि एक impl कुछ निश्चित फ़ंक्शंस को लागू (implement) करे। हमारे उदाहरण में, हमने impl Speed for Car सिंटैक्स का उपयोग करके Speed trait को impl Car से जोड़ा।
एक attribute-like macro एक struct को लेता है और इसे पूरी तरह से फिर से लिख सकता है।
एक derive macro अतिरिक्त फ़ंक्शंस के साथ एक struct को बढ़ाता है।
Macros Anchor को जटिलता छिपाने की अनुमति देते हैं
आइए उस प्रोग्राम को फिर से देखें जिसे Anchor anchor init के दौरान बनाता है:

attribute #[program] पर्दे के पीछे module को संशोधित कर रहा है। उदाहरण के लिए, यह एक राउटर लागू करता है जो स्वचालित रूप से आने वाले ब्लॉकचेन निर्देशों को module के भीतर उपयुक्त फ़ंक्शंस की ओर निर्देशित करता है।
Solana फ्रेमवर्क में उपयोग किए जाने के लिए struct Initialize {} को अतिरिक्त कार्यक्षमता के साथ बढ़ाया गया है।
सारांश (Summary)
Macros एक बहुत बड़ा विषय है। हमारा इरादा यहां आपको इस बात का एहसास कराना है कि जब आप #[program] या #[derive(Accounts)] देखते हैं तो क्या हो रहा होता है। यदि यह आपको अपरिचित लगता है तो निराश न हों। Solana प्रोग्राम लिखने के लिए आपको macros लिखने में सक्षम होने की आवश्यकता नहीं है।
हालाँकि, वे क्या करते हैं इसका अंदाज़ा होने से उम्मीद है कि आपके द्वारा देखे जाने वाले प्रोग्राम कम रहस्यमय लगेंगे।
RareSkills के साथ और जानें
यह ट्यूटोरियल हमारे मुफ़्त Solana course का हिस्सा है।
मूल रूप से 16 फरवरी, 2024 को प्रकाशित