تعریف و نمونهسازی از struct
یادآوری میکنم که تاپلها میتوانند مقادیر مرتبط با هم را نگه دارند و این مقادیر
میتوانند از نوعهای مختلفی باشند. struct هم این قابیتها را دارد اما علاوه بر اینها،
نامهایی را هم به مقادیرش اختصاص میدهد و به این ترتیب، به آنها معنا میدهد.
پس، struct نسبت به tuple انعطافپذیرتر است، چون برای تعیین مقادیر یا
دسترسی به آنها
نیازی به پایبند بودن به ترتیب آنها نیست. به هر آیتم یا مقدار میتوانیم با استفاده از نامش دسترسی داشته
باشیم.
برای تعریف یک ساختار از کلمه کلیدی struct استفاده میشود و بلافاصله بعد از آن،
نام ساختار کلی که قصد ایجاد آن را داریم، آورده میشود. سپس، یک بلاک برای این ساختار
ایجاد میکنیم و فیلدهای مربوط به ساختار را تعریف میکنیم. هر فیلد (field) شامل اعلام نام و نوع
یک مقدار است که با استفاده از سینتکس name: type ایجاد میشود. فیلدها را با کاما
از هم جدا میکنیم.
برای مثال، فرض کنید قصد داریم ساختاری تعریف کنیم که اطلاعات مربوط به حساب کاربری را نگه دارد.
src/main.rs
fn main() {}
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
بعد از تعریف یک ساختار، برای استفاده از آن، با فراهم کردن مقدار
برای هر یک از فیلدها، یک نمونه (instance) از آن ساختار ایجاد میکنیم. نمونهسازی از ساختار به
این صورت است که ابتدا نام ساختار و سپس یک بلاک برای مقداردهی به فیلدها ایجاد میکنیم.
یعنی فیلدهایی را که با استفاده از سینتکس name: type ایجاد کردیم، با استفاده از
سینتکس
name: value مقداردهی میکنیم و به این ترتیب، یک نمونه از ساختار بدست میآید.
نیازی هم نیست که فیلدها به همان ترتیبی که تعریف شدهاند، مقداردهی شوند.
بنابراین، میتوان گفت که تعریف یک ساختار حکم یک تمپلت کلی را برای یک نوع دارد و
نمونهها با استفاده از این تمپلت، مقادیری از آن نوع را ایجاد میکنند.
برای مثال، در اینجا با ایجاد یک نمونه از ساختار User یک یوزر خاص را ایجاد میکنیم.
src/main.rs
fn main() {
let user1 = User {
active: true,
username: String::from("tom"),
email: String::from("tom@example.com"),
sign_in_count: 1,
};
}
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
دسترسی به مقادیر فیلدهای یک ساختار با استفاده از سینتکس dot notaion امکانپذیر است.
برای مثال، با استفاده از user1.email میتوانیم به آدرس ایمیل یوزر دسترسی داشته
باشیم.
اگر نمونه به صورت mutable تعریف شده باشد، میتوانیم مقدار فیلدها را هم تغییر دهیم.
src/main.rs
fn main() {
let mut user1 = User {
active: true,
username: String::from("tom"),
email: String::from("tom@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
دقت داشته باشید که خود ساختار باید mutable باشد و Rust به ما اجازه نمیدهد که
یک فیلد را به صورت mutable تعریف کنیم.
یک نوع سفارشی مثل User میتواند مثل نوعهای Built-in به عنوان نوع پارامترهای توابع یا به عنوان
نوع بازگشتی توابع، تعیین شود. در کد زیر، تابعی تعریف شده که نوع بازگشتی آن User است.
یعنی باید یک نمونه از User را به عنوان خروجی برگرداند. بدنهی تابع شامل یک عبارت
(expression) است و میدانیم که توابع، آخرین عبارت موجود در بدنهی خود را به عنوان خروجی برمیگردانند.
src/main.rs
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
برای نمونهای که این تابع برمیگرداند، مقدار فیلد active برابر با true و مقدار فیلد
sign_in_count برابر با 1 خواهد بود اما فیلدهای username و email از روی ورودیهای
تابع مشخص میشوند. در مواردی مثل این که نام فیلدها و پارامترها یکی هستند،
می توانیم با تکیه بر سینتکسی به نام field init shorthand تابع را به شکل مختصرترِ زیر بنویسیم.
src/main.rs
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
ساخت نمونه با سینتکس Struct Update
گاهی اوقات میخواهیم نمونهای از یک ساختار ایجاد کنیم که اکثر مقادیرش
در یک نمونهی دیگر از آن ساختار، وجود دارد. در این موارد، میتوانیم به جای اینکه
نمونهی دوم را از صفر ایجاد کنیم، از سینتکس Stuct Update استفاده کنیم.
بدون استفاده از سینتکس struct update میتوانیم به شکل زیر یک نمونه
بسازیم که برخی مقادیرش را از یک نمونهی موجود، دریافت میکند.
src/main.rs
fn main() {
let user1 = User {
active: true,
username: String::from("tom"),
email: String::from("tom@example.com"),
sign_in_count: 1,
};
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
اما با استفاده از سینتکس struct update میتوانیم نتیجهی یکسانی را با کد کمتر بدست آوریم.
سینتکس .. به این معناست که فیلدهایی که صراحتاً مقداردهی نشدهاند، باید
مقدار خود را از نمونهی مورد نظر (در اینجا user1) دریافت کنند.
src/main.rs
fn main() {
let user1 = User {
active: true,
username: String::from("tom"),
email: String::from("tom@example.com"),
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
دقت داشته باشید که در این مثال، بعد از ایجاد user2 ما دیگر به user1 دسترسی
نخواهیم داشت. چون مقدار String مربوط به فیلد username در user1 به
user2 منتقل
(move) شده است. سیستم مالکیت را که فراموش نکرهاید! سینتکس struct update مثل عمل تخصیص (assignment)
کار میکند و
بنابراین، مقادیر نوعهایی مانند String را نه کپی، بلکه move میکند. توجه داشته باشید که
این مسئله
در مورد فیلدهای active و sign_in_count صادق نیست. چون اینها دارای نوعهای i32 و
u64 هستند که Stack-only هستند و
مقادیرشان در هنگام تخصیص، کپی میشوند.
کاربرد Tuple Struct
Rust نوع دیگری از ساختارها هم دارد که به خاطر شباهتی که به تاپلها دارند،
Tuple Struct نامیده میشوند. این ساختارها مثل ساختارهای معمولی، دارای نام هستند و
مثل تاپلها مقادیرشان نامی ندارند و صرفاً نوع آنهاست که هنگام تعریف، مشخص میشود.
پس، اینها در واقع، چیزی هستند بین تاپل و ساختار و به همین دلیل است که Tuple Struct نامیده میشوند.
یک tuple struct وقتی به کار میآید که میخواهیم به یک تاپل نامی بدهیم تا یک نوع
متفاوت تلقی شود اما از طرفی هم اینکه یک ساختار معمولی تعریف کنیم، کمی زیادهکاری است؛
چون نیاز نداریم که فیلدها نامی داشته باشد. مثال زیر، قضیه را روشن میکند.
src/main.rs
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
کد بالا اولاً روش تعریف tuple struct را نشان میدهد. کلمه کلیدی struct و نام و سپس،
نوع پارامترها درون پرانتز دیده میشوند. اما علاوه بر آن، این کد نشان میدهد که چرا این نوع ساختارها مفید
هستند.
در اینجا Color و Point هر دو ساختارهایی هستند که سه مقدار از نوع i32 را
نگه میدارند.
استفاده از تاپل برای تعریف اینها کار درستی نیست، چون نوع یکسانی به آنها میدهد در حالی که
Color و Point با هم تفاوت معنایی دارند و باید نوعهای مجزایی باشند. اما استفاده از ساختارهای
معمولی هم
گزینهی مناسبی نیست چون با اضافهکاری همراه است. موجودیتهای Color و Point برای مقادیرشان
نیازی به نام ندارند. در مورد Color مشخص است که مقدار اول به کانال رنگی R، مقدار دوم به
کانال رنگی
G و مقدار سوم به کانال رنگی B اختصاص دارد. پس، لزومی ندارد که ما سه فیلد مثلاً با نامهای
red، green و blue تعریف کنیم. همین داستان در مورد موجودیت Point نیز وجود دارد.
یعنی مقدار اول به مختص X، مقدار دوم به مختص Y و مقدار سوم به مختص Z تعلق دارد.
در اینگونه موارد، یک tuple struct گزینهی مناسب محسوب میشود.
الان Color و Point هم نوعهای مجزایی هستند و هم اضافهکاری ندارند.