All the Places Patterns Can Be Used
الگوها در بسیاری از جاها در راست ظاهر میشوند، و شما از آنها زیاد استفاده کردهاید بدون اینکه متوجه شوید! این بخش تمام جاهایی که الگوها معتبر هستند را بررسی میکند.
match
Arms
همانطور که در فصل 6 بحث شد، ما از الگوها در بازوهای (arms) عبارات match
استفاده میکنیم. بهطور رسمی، عبارات match
بهصورت کلمه کلیدی match
، یک مقدار برای مطابقت، و یک یا چند بازوی match که از یک الگو و یک عبارت برای اجرا در صورت مطابقت مقدار با الگوی آن بازو تشکیل شدهاند، تعریف میشوند، مانند این:
match VALUE {
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
PATTERN => EXPRESSION,
}
برای مثال، اینجا عبارت 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” بعداً در این فصل بهطور مفصل بررسی خواهیم کرد.
Conditional if let
Expressions
در فصل 6 بحث کردیم که چگونه از عبارات if let
عمدتاً بهعنوان یک روش کوتاهتر برای نوشتن معادل یک match
که فقط یک حالت را مطابقت میدهد استفاده کنیم. بهصورت اختیاری، if let
میتواند یک else
متناظر داشته باشد که شامل کدی برای اجرا در صورت عدم مطابقت الگو در if let
باشد.
فهرست 19-1 نشان میدهد که همچنین ممکن است عبارات if let
، else if
، و else if let
را با هم ترکیب و تطبیق دهید. این کار به ما انعطاف بیشتری نسبت به یک عبارت match
میدهد، که در آن فقط میتوانیم یک مقدار برای مقایسه با الگوها بیان کنیم. همچنین، راست نیاز ندارد که شرایط در یک سری از بازوهای if let
، else if
، else if let
به یکدیگر مرتبط باشند.
کد در فهرست 19-1 تعیین میکند که بر اساس یک سری بررسی برای چندین شرط، پسزمینه شما چه رنگی داشته باشد. برای این مثال، متغیرهایی با مقادیر سختکدشده ایجاد کردهایم که یک برنامه واقعی ممکن است از ورودی کاربر دریافت کند.
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
مشابه با ساختار if let
، حلقه شرطی while let
به یک حلقه while
اجازه میدهد تا زمانی که یک الگو همچنان مطابقت دارد، اجرا شود. اولین بار یک حلقه while let
را در فصل 17 دیدیم، جایی که از آن برای ادامه حلقه زدن تا زمانی که یک stream مقادیر جدید تولید میکرد استفاده کردیم. بهطور مشابه، در فهرست 19-2 یک حلقه while let
نشان داده میشود که منتظر پیامهایی است که بین نخها ارسال میشود، اما در این مورد یک Result
را بررسی میکند بهجای یک Option
.
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
را در فصل 16 دیدیم، خطا را مستقیماً unwrap میکردیم یا با استفاده از یک حلقه for
بهعنوان یک iterator با آن تعامل داشتیم. با این حال، همانطور که فهرست 19-2 نشان میدهد، میتوانیم از while let
نیز استفاده کنیم، زیرا متد recv
تا زمانی که فرستنده پیامها تولید میکند مقدار Ok
بازمیگرداند و سپس زمانی که طرف فرستنده قطع میشود یک مقدار Err
تولید میکند.
for
Loops
در یک حلقه for
، مقداری که مستقیماً بعد از کلمه کلیدی for
میآید یک الگو است. برای مثال، در عبارت for x in y
مقدار x
یک الگو است. فهرست 19-3 نشان میدهد که چگونه میتوان از یک الگو در یک حلقه for
برای تخریب (destructure) یا تجزیه یک tuple بهعنوان بخشی از حلقه for
استفاده کرد.
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کد در فهرست 19-3 خروجی زیر را چاپ خواهد کرد:
$ 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
ما یک iterator را با استفاده از متد enumerate
تطبیق میدهیم تا یک مقدار و شاخص آن مقدار را تولید کند، که در یک tuple قرار میگیرد. اولین مقدار تولیدشده tuple (0, 'a')
است. وقتی این مقدار با الگوی (index, value)
مطابقت داده میشود، مقدار index
برابر با 0
و مقدار value
برابر با 'a'
خواهد بود، و اولین خط خروجی چاپ میشود.
let
Statements
پیش از این فصل، ما بهطور خاص فقط درباره استفاده از الگوها با match
و if let
بحث کرده بودیم، اما در واقع، ما از الگوها در مکانهای دیگری نیز استفاده کردهایم، از جمله در عبارات let
. برای مثال، به این تخصیص ساده متغیر با let
توجه کنید:
#![allow(unused)] fn main() { let x = 5; }
هر بار که از یک عبارت let
مانند این استفاده کردهاید، از الگوها استفاده کردهاید، حتی اگر متوجه آن نشده باشید! بهطور رسمی، یک عبارت let
به این شکل است:
let PATTERN = EXPRESSION;
در عبارات مانند let x = 5;
با یک نام متغیر در محل PATTERN
، نام متغیر فقط یک شکل ساده از یک الگو است. راست عبارت را با الگو مقایسه میکند و هر نامی که پیدا میکند را تخصیص میدهد. بنابراین در مثال let x = 5;
، x
الگویی است که به این معناست: «هر چیزی که در اینجا مطابقت دارد را به متغیر x
اختصاص بده». چون نام x
کل الگو است، این الگو بهطور مؤثر به این معناست: «هر چیزی که هست را به متغیر x
اختصاص بده».
برای مشاهده جنبه تطبیق الگو در let
بهصورت واضحتر، فهرست 19-4 را در نظر بگیرید، که از یک الگو با let
برای تخریب یک tuple استفاده میکند.
fn main() { let (x, y, z) = (1, 2, 3); }
اینجا، ما یک tuple را با یک الگو مطابقت میدهیم. راست مقدار (1, 2, 3)
را با الگوی (x, y, z)
مقایسه میکند و میبیند که مقدار با الگو مطابقت دارد، بنابراین راست 1
را به x
، 2
را به y
، و 3
را به z
اختصاص میدهد. میتوانید این الگوی tuple را بهعنوان سه الگوی متغیر فردی که درون آن قرار دارند تصور کنید.
اگر تعداد عناصر در الگو با تعداد عناصر در tuple مطابقت نداشته باشد، کل نوع مطابقت نخواهد داشت و یک خطای کامپایلر دریافت خواهیم کرد. برای مثال، فهرست 19-5 یک تلاش برای تخریب یک tuple با سه عنصر به دو متغیر را نشان میدهد، که کار نخواهد کرد.
fn main() {
let (x, y) = (1, 2, 3);
}
تلاش برای کامپایل این کد منجر به این خطای type میشود:
$ 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
برای رفع خطا، میتوانیم یک یا چند مقدار در tuple را با استفاده از _
یا ..
نادیده بگیریم، همانطور که در بخش “Ignoring Values in a Pattern” خواهید دید. اگر مشکل این است که تعداد متغیرها در الگو بیش از حد است، راهحل این است که نوعها را با حذف متغیرها طوری تطبیق دهیم که تعداد متغیرها برابر با تعداد عناصر در tuple شود.
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) باشند. در بخش بعدی این دو مفهوم را بررسی خواهیم کرد.