All the Places Patterns Can Be Used
الگوها در بسیاری از جاها در راست ظاهر میشوند، و شما از آنها زیاد استفاده کردهاید بدون اینکه متوجه شوید! این بخش تمام جاهایی که الگوها معتبر هستند را بررسی میکند.
match
Arms
همانطور که در فصل 6 بحث شد، ما از الگوها در بازوهای (arms) عبارات match
استفاده میکنیم. بهطور رسمی، عبارات match
بهصورت کلمه کلیدی match
، یک مقدار برای مطابقت، و یک یا چند بازوی match که از یک الگو و یک عبارت برای اجرا در صورت مطابقت مقدار با الگوی آن بازو تشکیل شدهاند، تعریف میشوند، مانند این:
#![allow(unused)] fn main() { match <em>VALUE</em> { <em>PATTERN</em> => <em>EXPRESSION</em>, <em>PATTERN</em> => <em>EXPRESSION</em>, <em>PATTERN</em> => <em>EXPRESSION</em>, } }
برای مثال، در اینجا عبارت match
از لیستینگ 6-5 را داریم که روی یک مقدار از نوع Option<i32>
در متغیر x
تطبیق انجام میدهد:
match x {
None => None,
Some(i) => Some(i + 1),
}
الگوها در این عبارت match
شامل None
و Some(i)
هستند که در سمت چپ هر پیکان قرار دارند.
یکی از نیازمندیهای عبارات match
این است که باید بهصورت کامل باشند، به این معنا که تمام حالات ممکن برای مقدار در عبارت match
باید پوشش داده شوند. یکی از راههای اطمینان از اینکه همه حالات را پوشش دادهاید این است که یک الگوی عمومی (catchall) برای بازوی آخر داشته باشید: برای مثال، یک نام متغیر که هر مقداری را مطابقت میدهد هرگز شکست نمیخورد و بنابراین تمام موارد باقیمانده را پوشش میدهد.
الگوی خاص _
هر چیزی را مطابقت میدهد، اما هرگز به یک متغیر متصل نمیشود، بنابراین اغلب در بازوی آخر match استفاده میشود. الگوی _
میتواند زمانی مفید باشد که بخواهید هر مقداری که مشخص نشده است را نادیده بگیرید، برای مثال. ما الگوی _
را در بخش “Ignoring Values in a Pattern” بعداً در این فصل بهطور مفصل بررسی خواهیم کرد.
let Statements
پیش از این فصل، تنها بهطور صریح از الگوها در match
و if let
استفاده کرده بودیم، اما در واقع در بخشهای دیگری نیز از الگوها استفاده کردهایم، از جمله در دستورات let
. برای مثال، این انتساب ساده متغیر با استفاده از let
را در نظر بگیرید:
#![allow(unused)] fn main() { let x = 5; }
هر بار که از یک دستور let
مانند این استفاده کردهاید، در حال استفاده از یک الگو بودهاید، حتی اگر متوجه آن نشده باشید! بهطور رسمیتر، یک دستور let
به این شکل است:
let PATTERN = EXPRESSION;
در دستوراتی مانند let x = 5;
که یک نام متغیر در جایگاه PATTERN قرار دارد، آن نام متغیر در واقع شکل سادهای از یک الگو است. Rust عبارت را با الگو مقایسه میکند و هر نامی را که پیدا کند اختصاص میدهد. بنابراین، در مثال let x = 5;
، x
الگویی است که به این معناست: «هر چیزی که اینجا تطابق دارد را به متغیر x
متصل کن.» از آنجا که نام x
کل الگو را تشکیل میدهد، این الگو عملاً به این معناست: «هر چیزی که باشد، آن را به متغیر x
متصل کن.»
برای دیدن جنبهی تطبیق الگو در let
بهطور واضحتر، به کد موجود در لیست ۱۹-۱ نگاه کنید که از یک الگو با let
برای تجزیهی یک tuple استفاده میکند.
fn main() { let (x, y, z) = (1, 2, 3); }
در اینجا، ما یک tuple را با یک الگو تطبیق میدهیم. Rust مقدار (1, 2, 3)
را با الگوی (x, y, z)
مقایسه میکند و میبیند که این مقدار با الگو تطابق دارد، از این جهت که تعداد عناصر در هر دو یکی است. بنابراین Rust مقدار 1
را به x
، مقدار 2
را به y
، و مقدار 3
را به z
اختصاص میدهد. میتوانید این الگوی tuple را بهعنوان تو در تویی از سه الگوی متغیر مجزا در نظر بگیرید.
اگر تعداد عناصر در الگو با تعداد عناصر در tuple تطابق نداشته باشد، نوع کلی تطابق نخواهد داشت و با خطای کامپایل مواجه خواهیم شد. برای مثال، لیست ۱۹-۲ تلاشی برای تجزیهی یک tuple با سه عنصر به دو متغیر را نشان میدهد، که کار نخواهد کرد.
fn main() {
let (x, y) = (1, 2, 3);
}
تلاش برای کامپایل این کد منجر به این خطای نوع میشود:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
error[E0308]: mismatched types
--> src/main.rs:2:9
|
2 | let (x, y) = (1, 2, 3);
| ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})`
| |
| expected a tuple with 3 elements, found one with 2 elements
|
= note: expected tuple `({integer}, {integer}, {integer})`
found tuple `(_, _)`
For more information about this error, try `rustc --explain E0308`.
error: could not compile `patterns` (bin "patterns") due to 1 previous error
برای رفع این خطا، میتوانیم یک یا چند مقدار را با استفاده از _
یا ..
نادیده بگیریم، همانطور که در بخش [«نادیده گرفتن مقادیر در یک الگو»][ignoring-values-in-a-pattern] خواهید دید. اگر مشکل این باشد که متغیرهای زیادی در الگو داریم، راهحل این است که برخی متغیرها را حذف کنیم تا تعداد متغیرها با تعداد عناصر در tuple برابر شود.
عبارات شرطی if let
در فصل ۶، در مورد نحوهی استفاده از عبارات if let
صحبت کردیم، عمدتاً بهعنوان راهی کوتاهتر برای نوشتن معادل match
که تنها یک حالت را تطبیق میدهد. بهصورت اختیاری، if let
میتواند یک بخش else
نیز داشته باشد که در صورت عدم تطابق الگو، کدی را اجرا کند.
لیست ۱۹-۳ نشان میدهد که همچنین میتوانیم if let
، else if
، و else if let
را ترکیب کنیم. این کار انعطافپذیری بیشتری نسبت به یک عبارت match
به ما میدهد، که در آن تنها میتوانیم یک مقدار را با الگوها مقایسه کنیم. همچنین، Rust الزام نمیکند که شرایط در زنجیرهی if let
، else if
، و else if let
به یکدیگر مرتبط باشند.
کد موجود در لیست ۱۹-۳ تعیین میکند که پسزمینهی شما بر اساس مجموعهای از بررسیها چه رنگی باشد. برای این مثال، متغیرهایی با مقادیر hardcoded ایجاد کردهایم که یک برنامه واقعی ممکن است از ورودی کاربر دریافت کند.
fn main() { let favorite_color: Option<&str> = None; let is_tuesday = false; let age: Result<u8, _> = "34".parse(); if let Some(color) = favorite_color { println!("Using your favorite color, {color}, as the background"); } else if is_tuesday { println!("Tuesday is green day!"); } else if let Ok(age) = age { if age > 30 { println!("Using purple as the background color"); } else { println!("Using orange as the background color"); } } else { println!("Using blue as the background color"); } }
if let
، else if
، else if let
و else
اگر کاربر یک رنگ مورد علاقه مشخص کند، از آن رنگ بهعنوان پسزمینه استفاده میشود. اگر هیچ رنگ مورد علاقهای مشخص نشده باشد و امروز سهشنبه باشد، رنگ پسزمینه سبز است. در غیر این صورت، اگر کاربر سن خود را بهعنوان یک رشته مشخص کند و بتوانیم آن را با موفقیت به یک عدد تبدیل کنیم، رنگ یا بنفش یا نارنجی است، بسته به مقدار عدد. اگر هیچکدام از این شرایط صدق نکند، رنگ پسزمینه آبی خواهد بود.
این ساختار شرطی به ما امکان پشتیبانی از نیازهای پیچیده را میدهد. با مقادیر سختکدشدهای که در اینجا داریم، این مثال پیام Using purple as the background color
را چاپ خواهد کرد.
میتوانید ببینید که if let
نیز میتواند متغیرهای جدیدی را معرفی کند که متغیرهای موجود را به همان روشی که بازوهای match
انجام میدهند، پوشش میدهند: خط if let Ok(age) = age
یک متغیر جدید به نام age
معرفی میکند که حاوی مقدار داخل حالت Ok
است و متغیر موجود age
را پوشش میدهد. این بدان معناست که باید شرط if age > 30
را در داخل آن بلوک قرار دهیم: نمیتوانیم این دو شرط را بهصورت if let Ok(age) = age && age > 30
ترکیب کنیم. متغیر جدید age
که میخواهیم با 30 مقایسه کنیم تا شروع محدوده جدید با آکولاد معتبر نیست.
نقطه ضعف استفاده از عبارات if let
این است که کامپایلر بررسی نمیکند که آیا همه حالات پوشش داده شدهاند یا خیر، در حالی که با عبارات match
این کار را انجام میدهد. اگر بلوک آخر else
را حذف کنیم و بنابراین برخی موارد را پوشش ندهیم، کامپایلر به ما در مورد باگ احتمالی منطقی هشدار نمیدهد.
while let
Conditional Loops
ساختار while let
مشابه if let
است و به ما این امکان را میدهد که یک حلقهی while
تا زمانی که یک الگو با موفقیت تطبیق یابد، اجرا شود. در لیست ۱۹-۴، یک حلقهی while let
را نشان میدهیم که منتظر دریافت پیامهایی است که بین نخها (threads) ارسال میشوند، اما در این مورد به جای بررسی یک مقدار از نوع Option
، یک مقدار از نوع Result
را بررسی میکنیم.
fn main() { let (tx, rx) = std::sync::mpsc::channel(); std::thread::spawn(move || { for val in [1, 2, 3] { tx.send(val).unwrap(); } }); while let Ok(value) = rx.recv() { println!("{value}"); } }
while let
برای چاپ مقادیر تا زمانی که rx.recv()
مقدار Ok
برگردانداین مثال مقادیر 1
، 2
، و سپس 3
را چاپ میکند. متد recv
اولین پیام را از سمت گیرندهی کانال دریافت کرده و یک Ok(value)
برمیگرداند. زمانی که در فصل ۱۶ با recv
آشنا شدیم، یا مستقیماً خطا را unwrap
میکردیم، یا از آن بهعنوان یک پیمایشگر (iterator) در یک حلقهی for
استفاده میکردیم. اما همانطور که در لیست ۱۹-۴ نشان داده شده است، میتوانیم از while let
نیز استفاده کنیم، زیرا متد recv
هر بار که پیامی دریافت شود، یک Ok
برمیگرداند، تا زمانی که فرستنده هنوز وجود داشته باشد، و زمانی که سمت فرستنده قطع شود، یک Err
تولید میکند.
حلقههای for
In a for
loop, the value that directly follows the keyword for
is a
pattern. For example, in for x in y
, the x
is the pattern. Listing 19-5
demonstrates how to use a pattern in a for
loop to destructure, or break
apart, a tuple as part of the for
loop.
fn main() { let v = vec!['a', 'b', 'c']; for (index, value) in v.iter().enumerate() { println!("{value} is at index {index}"); } }
for
loop to destructure a tupleکدی که در لیست ۱۹-۵ آمده است، خروجی زیر را چاپ خواهد کرد:
$ cargo run
Compiling patterns v0.1.0 (file:///projects/patterns)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.52s
Running `target/debug/patterns`
a is at index 0
b is at index 1
c is at index 2
ما با استفاده از متد enumerate
یک پیمایشگر (iterator) را تطبیق میدهیم تا همراه با هر مقدار، اندیس آن مقدار را نیز تولید کند؛ این دو مقدار در قالب یک tuple قرار میگیرند. اولین مقداری که تولید میشود tupleای به شکل (0, 'a')
است. هنگامی که این مقدار با الگوی (index, value)
تطبیق داده میشود، index
برابر با 0
و value
برابر با 'a'
خواهد بود، و خط اول از خروجی چاپ میشود.
Function Parameters
پارامترهای تابع نیز میتوانند الگو باشند. کد در فهرست 19-6، که تابعی به نام foo
را تعریف میکند که یک پارامتر به نام x
از نوع i32
میگیرد، باید تا الان آشنا به نظر برسد.
fn foo(x: i32) { // code goes here } fn main() {}
قسمت x
یک الگو است! همانطور که با let
انجام دادیم، میتوانیم یک tuple را در آرگومانهای یک تابع با الگو مطابقت دهیم. فهرست 19-7 مقادیر یک tuple را هنگام ارسال به یک تابع تجزیه میکند.
fn print_coordinates(&(x, y): &(i32, i32)) { println!("Current location: ({x}, {y})"); } fn main() { let point = (3, 5); print_coordinates(&point); }
این کد پیام Current location: (3, 5)
را چاپ میکند. مقادیر &(3, 5)
با الگوی &(x, y)
مطابقت دارند، بنابراین x
مقدار 3
و y
مقدار 5
است.
ما همچنین میتوانیم از الگوها در لیست پارامترهای closureها به همان روشی که در لیست پارامترهای تابع استفاده میکنیم، استفاده کنیم، زیرا closureها شبیه به توابع هستند، همانطور که در فصل 13 بحث شد.
تا اینجا، چندین روش برای استفاده از الگوها را دیدهاید، اما الگوها در هر جایی که از آنها استفاده کنیم به یک شکل کار نمیکنند. در برخی مکانها، الگوها باید غیرقابلرد (irrefutable) باشند؛ در شرایط دیگر، میتوانند قابلرد (refutable) باشند. در بخش بعدی این دو مفهوم را بررسی خواهیم کرد.