ماکرو در مقابل توابع در زنگ زدگی: چه موقع از کدام یک استفاده کنید؟

هنگام رشد در زنگ زدگی ، ما اغلب با یک معضل روبرو هستیم: چه موقع باید از ماکرو استفاده کنیم تا کد خود را ساده کنیم و چه زمانی باید به جای آن به توابع اعتماد کنیم؟
در این مقاله سناریوهای استفاده از ماکروها ، به شما در درک مناسب ماکروها کمک می کند. بیایید با نتیجه گیری شروع کنیم:
ماکروها و توابع قابل تعویض نیستند بلکه مکمل هستند. هر کدام نقاط قوت خاص خود را دارند و فقط با استفاده از آنها به درستی می توانیم کد زنگ زدگی عالی را بنویسیم.
حال ، بیایید موارد استفاده را برای ماکروها بررسی کنیم.
دسته های ماکرو
ماکروها در زنگ زدگی به:
-
ماکروهای اعلامی (
macro_rules!
) - ماکروهای رویه
ماکروهای رویه ای را می توان بیشتر به:
- ماکروهای مشتق شده سفارشی
- ماکرو
- ماکروهای عملکردی مانند
در زنگ زدگی ، هر دو عملکرد و ماکرو به عنوان ابزاری اساسی برای استفاده مجدد از کد و انتزاع عمل می کنند. توابع منطق را محاصره می کنند ، تعداد مشخصی از پارامترها را با انواع شناخته شده کنترل می کنند و نوع ایمنی و خوانایی را ارائه می دهند. از طرف دیگر ، ماکروها در زمان کامپایل کد تولید می کنند و قابلیت هایی را که توابع نمی توانند به دست آورند ، مانند دستیابی به یک شماره متغیر و نوع پارامترها ، تولید کد و استفاده از سیستم سازی ، امکان پذیر می شوند.
موارد استفاده خاص
ماکروهای اعلامی (macro_rules!
)
سناریو: رسیدگی به یک عدد متغیر و نوع پارامترها
توضیحات مشکل:
- توابع باید تعداد و انواع پارامترها را در تعریف مشخص کنند و به طور مستقیم نمی توانند یک شماره متغیر یا نوع پارامترها را بپذیرند.
- مکانیسم لازم برای رسیدگی به عملکردهای مانند
println!
، که یک شماره و نوع دلخواه را می پذیرد.
راه حل کلان:
- ماکروهای اعلانی از الگوی تطبیق برای پذیرش اعداد دلخواه و انواع پارامترها استفاده می کنند.
- الگوهای تکرار (
$()*
) و metavariables ($var
) برای ضبط لیست های پارامتر استفاده می شود.
کد مثال:
// Define a macro that accepts variable arguments
macro_rules! my_println {
($($arg:tt)*) => {
println!($($arg)*);
};
}
fn main() {
my_println!("Hello, world!");
my_println!("Number: {}", 42);
my_println!("Multiple values: {}, {}, {}", 1, 2, 3);
}
محدودیت توابع:
- توابع نمی توانند امضایی را که اعداد دلخواه و انواع پارامترها را می پذیرند ، تعریف کنند.
- حتی با استفاده از پارامترهای متغیر ، Rust به طور مستقیم بدون ساختارهای خاص مانند آنها از آنها پشتیبانی نمی کند
format_args!
بشر
هماهنگی بین ماکروها و توابع:
- ماکروها پارامترها را جمع آوری و گسترش می دهند ، سپس توابع اساسی را فراخوانی می کنند (به عنوان مثال
println!
در نهایت تماس می گیردstd::io::stdout().write_fmt()
). - توابع منطق اجرای اصلی را اداره می کنند ، در حالی که ماکرو پارامترهای را تجزیه می کنند و کد تولید می کنند.
سناریو: ساده سازی الگوهای کد تکراری
توضیحات مشکل:
- هنگامی که بسیاری از الگوهای کد تکراری مانند موارد آزمایش یا دسترسی به زمینه وجود دارد.
- نوشتن چنین کدی به صورت دستی مستعد خطا است و هزینه های نگهداری بالایی دارد.
راه حل کلان:
- ماکروهای اعلانی برای تولید ساختارهای کد تکراری به طور خودکار مطابقت دارند.
- استفاده از ماکرو تلاش دستی برای نوشتن کد تکراری را کاهش می دهد.
کد مثال:
// Define a macro to generate getter methods for a struct
macro_rules! generate_getters {
($struct_name:ident, $($field:ident),*) => {
impl $struct_name {
$(
pub fn $field(&self) -> &str {
&self.$field
}
)*
}
};
}
struct Person {
name: String,
email: String,
}
generate_getters!(Person, name, email);
fn main() {
let person = Person {
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
println!("Name: {}", person.name());
println!("Email: {}", person.email());
}
محدودیت توابع:
- توابع نمی توانند چندین توابع را بر اساس ورودی در زمان تعریف ایجاد کنند و نیاز به نوشتن دستی از هر روش گیرنده دارند.
- توابع فاقد تولید کد در زمان کامپایل و قابلیت برنامه نویسی استعاره هستند.
هماهنگی بین ماکروها و توابع:
- ماکروها کد تولید می کنند و اجرای عملکرد را ایجاد می کنند.
- توابع به عنوان موجودات قابل تماس نهایی تولید شده توسط ماکروها خدمت می کنند.
سناریو: اجرای DSL های کوچک تعبیه شده
توضیحات مشکل:
- نیاز به نحو طبیعی تر و خاص دامنه برای تقویت خوانایی و بیان.
- تمایل به تعبیه ساختارهای نحوی شبیه به سایر زبانها ، مانند HTML یا SQL ، به طور مستقیم در کد.
راه حل کلان:
- ماکروهای اعلامی می توانند با الگوهای نحوی خاص مطابقت داشته و کد زنگ مربوط را تولید کنند.
- تطبیق الگوی بازگشتی اجازه می دهد تا DSL های تعبیه شده (زبانهای خاص دامنه) ایجاد شود.
کد مثال:
// A simple HTML DSL macro
macro_rules! html {
// Match a tag with inner content
($tag:ident { $($inner:tt)* }) => {
format!("<{tag}>{content}{tag}>", tag=stringify!($tag), content=html!($($inner)*))
};
// Match a text node
($text:expr) => {
$text.to_string()
};
// Match multiple child nodes
($($inner:tt)*) => {
vec![$(html!($inner)),*].join("")
};
}
fn main() {
let page = html! {
html {
head {
title { "My Page" }
}
body {
h1 { "Welcome!" }
p { "This is a simple HTML page." }
}
}
};
println!("{}", page);
}
محدودیت توابع:
- توابع نمی توانند ساختارهای نحوی سفارشی را بپذیرند یا تجزیه کنند. پارامترها باید عبارات زنگ زده معتبر باشند.
- توابع نمی توانند نحو تو در تو را به روشی بصری فراهم کنند و منجر به کد لفظی و کمتر قابل خواندن شوند.
هماهنگی بین ماکروها و توابع:
- ماکرو ساختارهای نحوی سفارشی را تجزیه کرده و آنها را به کد زنگ تبدیل می کند.
- توابع منطق اصلی را اجرا می کنند ، مانند
format!
یا هماهنگی رشته ای.
ماکروهای رویه
ماکروهای رویه ای نوع قدرتمندتری از کلان هستند که می توانند درخت نحوی انتزاعی زنگ زدگی (AST) را برای تولید و تحول کد پیچیده دستکاری کنند. آنها عمدتا در طبقه بندی می شوند:
- ماکروهای مشتق شده سفارشی
- ماکرو
- ماکروهای عملکردی مانند
ماکروهای مشتق شده سفارشی
سناریو: اجرای خودکار صفات برای انواع
توضیحات مشکل:
- نیاز به اجرای خودکار یک صفت (به عنوان مثال ،
Debug
باClone
باSerialize
، و غیره) برای انواع مختلف برای جلوگیری از نوشتن کد تکراری. - نیاز به تولید کد اجرای به صورت پویا بر اساس ویژگی های نوع است.
راه حل کلان:
- ماکروهای سفارشی تعاریف نوع را در زمان کامپایل تجزیه و تحلیل کرده و بر این اساس پیاده سازی های صفت را ایجاد می کنند.
- موارد استفاده متداول شامل صفات مشتق خودکار مانند
serde
سریال سازی/deserialization یا داخلیDebug
وتClone
ویژگی ها
کد مثال:
// Import necessary macro support
use serde::{Serialize, Deserialize};
// Use a custom derive macro to automatically implement Serialize and Deserialize
#[derive(Serialize, Deserialize)]
struct Person {
name: String,
age: u8,
}
fn main() {
let person = Person {
name: "Alice".to_string(),
age: 30,
};
// Serialize to JSON string
let json = serde_json::to_string(&person).unwrap();
println!("Serialized: {}", json);
// Deserialize back into a struct
let deserialized: Person = serde_json::from_str(&json).unwrap();
println!("Deserialized: {} is {} years old.", deserialized.name, deserialized.age);
}
محدودیت توابع:
- توابع نمی توانند به طور خودکار پیاده سازی های صفت را بر اساس تعاریف نوع تولید کنند.
- توابع نمی توانند زمینه ها یا ویژگی های ساختاری را در زمان کامپایل برای تولید کد مربوطه بازرسی کنند.
هماهنگی بین ماکروها و توابع:
- ماکروهای مشتق شده سفارشی کد اجرای صفت مورد نیاز را ایجاد کنید.
- توابع منطق رفتار هر صفت را ارائه دهید.
ماکرو
سناریو: اصلاح عملکرد یا رفتار نوع
توضیحات مشکل:
- نیاز به تغییر عملکرد یا نوع رفتار در زمان کامپایل ، مانند اضافه کردن خودکار ورود به سیستم ، پروفایل عملکرد یا تزریق منطق اضافی.
- ترجیح می دهید به جای اصلاح دستی هر عملکرد ، از حاشیه نویسی استفاده کنید.
راه حل کلان:
- ماکروهای ویژگی را می توان به توابع ، انواع یا ماژول ها برای اصلاح یا تولید کد جدید در زمان کامپایل وصل کرد.
- این ماکروها یک روش انعطاف پذیر برای تقویت رفتار کد بدون تغییر مستقیم تعاریف عملکرد ارائه می دهند.
کد مثال:
// Define a simple attribute macro that prints logs before and after a function executes
use proc_macro::TokenStream;
#[proc_macro_attribute]
pub fn log_execution(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(item as syn::ItemFn);
let fn_name = &input.sig.ident;
let block = &input.block;
let expanded = quote::quote! {
fn #fn_name() {
println!("Entering function {}", stringify!(#fn_name));
#block
println!("Exiting function {}", stringify!(#fn_name));
}
};
TokenStream::from(expanded)
}
// Use the attribute macro
#[log_execution]
fn my_function() {
println!("Function body");
}
fn main() {
my_function();
}
محدودیت توابع:
- توابع نمی توانند رفتار اجرای خود را در خارج تغییر دهند. آنها باید به صورت دستی شامل کد ورود یا پروفایل باشند.
- توابع مکانیسم داخلی برای تزریق رفتار به صورت پویا در زمان کامپایل ندارند.
هماهنگی بین ماکروها و توابع:
- ماکرو با تزریق منطق اضافی ، تعریف عملکرد را در زمان کامپایل تغییر دهید.
- توابع روی منطق اصلی تجارت آنها متمرکز شده اند.
ماکروهای عملکردی مانند
سناریو: ایجاد نحو سفارشی یا تولید کد
توضیحات مشکل:
- نیاز به پذیرش فرمت های ورودی خاص و تولید کد زنگ مربوطه ، مانند تنظیم تنظیمات اولیه یا تولید جداول مسیریابی.
- می خواهید از یک نحو شبیه به عملکرد استفاده کنید (
my_macro!(...)
) برای تعریف منطق سفارشی.
راه حل کلان:
- ماکروهای عملکردی مانند
TokenStream
ورودی ، پردازش آن و تولید کد زنگ جدید. - آنها برای سناریوهایی که نیاز به تجزیه پیچیده و تولید کد دارند ، مناسب هستند.
کد مثال:
// Define a function macro that converts a string to uppercase at compile time
use proc_macro::TokenStream;
#[proc_macro]
pub fn make_uppercase(input: TokenStream) -> TokenStream {
let s = input.to_string();
let uppercased = s.to_uppercase();
let output = quote::quote! {
#uppercased
};
TokenStream::from(output)
}
// Use the function macro
fn main() {
let s = make_uppercase!("hello, world!");
println!("{}", s); // Output: HELLO, WORLD!
}
محدودیت توابع:
- توابع نمی توانند در زمان کامپایل تحت اللفظی رشته ها را تغییر دهند. همه تحولات در زمان اجرا اتفاق می افتد.
- تحولات زمان اجرا در مقایسه با تحولات زمان کامپایل ، عملکرد سربار اضافی دارد.
هماهنگی بین ماکروها و توابع:
- ماکروهای عملکردی مانند کد یا داده های مورد نیاز را در زمان کامپایل تولید کنید.
- توابع در زمان اجرا روی کد تولید شده کار کنید.
چگونه بین ماکروها و توابع انتخاب کنیم؟
در توسعه عملی ، انتخاب بین ماکروها و توابع باید بر اساس نیازهای خاص باشد:
توابع را در صورت امکان ترجیح دهید
هر وقت مشکلی با یک عملکرد حل شود ، توابع باید اولین انتخاب باشند به دلیل آنها:
- خوانایی
- حفظ قابلیت
- ایمنی تایپ
- اشکال زدایی و تست سهولت
در صورت عدم کارکردها از ماکرو استفاده کنید
از ماکروها در سناریوهایی که توابع در آن قرار دارند استفاده کنید کافی نیست، مانند:
-
رسیدگی به یک شماره متغیر و نوع پارامترها (به عنوان مثال ،
println!
). - تولید کد تکراری در زمان کامپایل برای جلوگیری از دیگ بخار (به عنوان مثال ، گیرنده های پخش خودکار).
-
ایجاد DSL های جاسازی شده برای نحو خاص دامنه (به عنوان مثال ،
html!
). -
اجرای خودکار صفات (به عنوان مثال ،
#[derive(Serialize, Deserialize)]
). -
اصلاح ساختار یا رفتار کد در زمان کامپایل (به عنوان مثال ،
#[log_execution]
).
موقعیت هایی که توابع نسبت به ماکروها ارجحیت دارند
- رسیدگی به منطق پیچیده تجارت → توابع برای اجرای منطق پیچیده و الگوریتم ها مناسب تر هستند.
- اطمینان از نوع ایمنی و بررسی خطا → توابع دارای امضاهای نوع صریح هستند و به کامپایلر Rust اجازه می دهند خطاها را بررسی کنند.
- خوانایی و قابلیت حفظ کد → توابع ساختار یافته و آسان تر از ماکروها هستند که به کد پیچیده گسترش می یابند.
- سهولت اشکال زدایی و آزمایش → توابع می توانند راحت تر از ماکروها آزمایش و اشکال زدایی شوند ، که اغلب پیام های خطای مبهم تولید می کنند.
افکار نهایی
با پیروی از این دستورالعمل ها ، می توانید در مورد استفاده از ماکرو یا توابع در پروژه های زنگ زدگی خود تصمیم آگاهانه بگیرید. ترکیب هر دو به طور مؤثر به شما در نوشتن کد زنگ زدگی کارآمدتر ، قابل حفظ و مقیاس پذیر کمک می کند.
ما Leapcell ، انتخاب برتر شما برای میزبانی پروژه های زنگ زدگی هستیم.
Leapcell بستر سرور نسل بعدی برای میزبانی وب ، کارهای ASYNC و REDIS است:
پشتیبانی چند زبانی
- با node.js ، پایتون ، برو یا زنگ زدگی توسعه دهید.
پروژه های نامحدود را به صورت رایگان مستقر کنید
- فقط برای استفاده پرداخت کنید – بدون درخواست ، بدون هزینه.
راندمان هزینه بی نظیر
- پرداخت به عنوان شما بدون هیچ گونه هزینه بیکار.
- مثال: 25 دلار از درخواست های 6.94M در زمان پاسخ متوسط 60ms پشتیبانی می کند.
تجربه توسعه دهنده ساده
- UI بصری برای راه اندازی بی دردسر.
- خطوط لوله CI/CD کاملاً خودکار و ادغام GITOPS.
- معیارهای زمان واقعی و ورود به سیستم برای بینش های عملی.
مقیاس پذیری بی دردسر و عملکرد بالا
- مقیاس خودکار برای رسیدگی به همزمانی بالا با سهولت.
- صفر سربار عملیاتی – فقط روی ساختمان تمرکز کنید.
در اسناد بیشتر کاوش کنید!
ما را در X دنبال کنید: LeapCellHQ
در وبلاگ ما بخوانید