ویژگیها (Traits): تعریف رفتار مشترک
یک ویژگی (trait) عملکردی را که یک نوع خاص دارد تعریف میکند و میتواند با انواع دیگر به اشتراک بگذارد. ما میتوانیم از traitها برای تعریف رفتار مشترک به صورت انتزاعی استفاده کنیم. همچنین میتوانیم از محدودیتهای ویژگی (trait bounds) برای مشخص کردن اینکه یک نوع جنریک میتواند هر نوعی باشد که رفتار خاصی دارد، استفاده کنیم.
توجه: ویژگیها شبیه به مفهومی هستند که اغلب در زبانهای دیگر به نام interfaces شناخته میشود، البته با برخی تفاوتها.
تعریف یک trait
رفتار یک نوع شامل متدهایی است که میتوانیم روی آن نوع فراخوانی کنیم. انواع مختلف یک رفتار مشترک دارند اگر بتوانیم همان متدها را روی تمام آن انواع فراخوانی کنیم. تعریف ویژگیها راهی برای گروهبندی امضاهای متدها با هم است تا مجموعهای از رفتارها را که برای دستیابی به یک هدف خاص ضروری است، تعریف کنیم.
برای مثال، فرض کنید چندین ساختار داده داریم که انواع و مقادیر مختلفی از متن را نگه میدارند: یک ساختار NewsArticle
که یک خبر ذخیره شده در یک مکان خاص را نگه میدارد و یک ساختار Tweet
که میتواند حداکثر ۲۸۰ کاراکتر به همراه متادیتایی که نشان میدهد آیا این یک توییت جدید، بازتوییت، یا پاسخ به توییت دیگری بوده است را نگه دارد.
ما میخواهیم یک کتابخانه گردآورنده رسانه به نام aggregator
ایجاد کنیم که بتواند خلاصههایی از دادههایی که ممکن است در یک نمونه از NewsArticle
یا Tweet
ذخیره شده باشند، نمایش دهد. برای این کار، نیاز به خلاصهای از هر نوع داریم و این خلاصه را با فراخوانی متد summarize
روی یک نمونه درخواست خواهیم کرد. لیست ۱۰-۱۲ تعریف یک ویژگی عمومی Summary
را نشان میدهد که این رفتار را بیان میکند.
pub trait Summary {
fn summarize(&self) -> String;
}
Summary
که شامل رفتار ارائهشده توسط یک متد summarize
استدر اینجا، یک ویژگی با استفاده از کلیدواژه trait
و سپس نام ویژگی، که در اینجا Summary
است، اعلام میکنیم. همچنین ویژگی را به عنوان pub
اعلام میکنیم تا کرایتهایی که به این کرایت وابسته هستند نیز بتوانند از این ویژگی استفاده کنند، همانطور که در چند مثال خواهیم دید. در داخل آکولادها، امضاهای متدی را اعلام میکنیم که رفتارهای نوعهایی که این ویژگی را پیادهسازی میکنند توصیف میکنند، که در این مورد fn summarize(&self) -> String
است.
بعد از امضای متد، به جای ارائه یک پیادهسازی در داخل آکولادها، از یک نقطهویرگول استفاده میکنیم. هر نوعی که این ویژگی را پیادهسازی میکند باید رفتار سفارشی خود را برای بدنه متد ارائه دهد. کامپایلر اطمینان خواهد داد که هر نوعی که ویژگی Summary
را دارد، متد summarize
را دقیقاً با این امضا تعریف خواهد کرد.
یک ویژگی میتواند چندین متد در بدنه خود داشته باشد: امضاهای متدها به صورت یک خط در هر خط فهرست میشوند و هر خط با یک نقطهویرگول پایان مییابد.
پیادهسازی یک ویژگی (trait) روی یک نوع
اکنون که امضاهای مورد نظر متدهای ویژگی Summary
را تعریف کردهایم، میتوانیم آن را روی نوعهای موجود در گردآورنده رسانه خود پیادهسازی کنیم. لیست ۱۰-۱۳ یک پیادهسازی از ویژگی Summary
روی ساختار NewsArticle
را نشان میدهد که از تیتر، نویسنده، و مکان برای ایجاد مقدار بازگشتی summarize
استفاده میکند. برای ساختار Tweet
، متد summarize
را به صورت نام کاربری به همراه تمام متن توییت تعریف میکنیم، با فرض اینکه محتوای توییت قبلاً به ۲۸۰ کاراکتر محدود شده است.
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Summary
روی نوعهای NewsArticle
و Tweet
پیادهسازی یک ویژگی روی یک نوع مشابه پیادهسازی متدهای معمولی است. تفاوت این است که بعد از impl
، نام ویژگیای که میخواهیم پیادهسازی کنیم را قرار میدهیم، سپس از کلمه کلیدی for
استفاده میکنیم و سپس نام نوعی که میخواهیم ویژگی را برای آن پیادهسازی کنیم مشخص میکنیم. درون بلوک impl
، امضاهای متدی که تعریف ویژگی مشخص کردهاند را قرار میدهیم. به جای اضافه کردن یک نقطهویرگول بعد از هر امضا، از آکولادها استفاده میکنیم و بدنه متد را با رفتار خاصی که میخواهیم متدهای ویژگی برای نوع خاص داشته باشند پر میکنیم.
حالا که کتابخانه ویژگی Summary
را روی NewsArticle
و Tweet
پیادهسازی کرده است، کاربران این کرایت میتوانند متدهای ویژگی را روی نمونههای NewsArticle
و Tweet
فراخوانی کنند، به همان روشی که متدهای معمولی را فراخوانی میکنیم. تنها تفاوت این است که کاربر باید ویژگی را به همراه نوعها به محدوده وارد کند. در اینجا مثالی از اینکه چگونه یک کرایت باینری میتواند از کرایت کتابخانه aggregator
ما استفاده کند آورده شده است:
use aggregator::{Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
این کد 1 new tweet: horse_ebooks: of course, as you probably already know, people
را چاپ میکند.
کرایتهای دیگری که به کرایت aggregator
وابسته هستند نیز میتوانند ویژگی Summary
را به محدوده وارد کنند تا Summary
را روی نوعهای خودشان پیادهسازی کنند. یکی از محدودیتهایی که باید به آن توجه داشت این است که ما فقط میتوانیم یک ویژگی را روی یک نوع پیادهسازی کنیم اگر یا ویژگی یا نوع، یا هر دو، به کرایت ما محلی باشند. برای مثال، ما میتوانیم ویژگیهایی از کتابخانه استاندارد مانند Display
را روی یک نوع سفارشی مانند Tweet
به عنوان بخشی از عملکرد کرایت aggregator
پیادهسازی کنیم زیرا نوع Tweet
به کرایت aggregator
محلی است. همچنین میتوانیم Summary
را روی Vec<T>
در کرایت aggregator
پیادهسازی کنیم زیرا ویژگی Summary
به کرایت aggregator
محلی است.
اما نمیتوانیم ویژگیهای خارجی را روی نوعهای خارجی پیادهسازی کنیم. برای مثال، نمیتوانیم ویژگی Display
را روی Vec<T>
در کرایت aggregator
پیادهسازی کنیم زیرا Display
و Vec<T>
هر دو در کتابخانه استاندارد تعریف شدهاند و به کرایت aggregator
محلی نیستند. این محدودیت بخشی از خاصیتی به نام انسجام (coherence) و به طور خاصتر قانون یتیم (orphan rule) است، که به این دلیل نامگذاری شده است که نوع والد وجود ندارد. این قانون اطمینان میدهد که کد دیگران نمیتواند کد شما را خراب کند و برعکس. بدون این قانون، دو کرایت میتوانستند همان ویژگی را برای همان نوع پیادهسازی کنند و Rust نمیدانست کدام پیادهسازی را استفاده کند.
پیادهسازیهای پیشفرض
گاهی اوقات مفید است که رفتار پیشفرضی برای برخی یا همه متدهای یک ویژگی داشته باشید به جای اینکه پیادهسازیها برای تمام متدها در هر نوع اجباری باشند. سپس، وقتی ویژگی را روی یک نوع خاص پیادهسازی میکنیم، میتوانیم رفتار پیشفرض هر متد را نگه داریم یا جایگزین کنیم.
در لیست ۱۰-۱۴، یک رشته پیشفرض برای متد summarize
ویژگی Summary
مشخص میکنیم به جای اینکه فقط امضای متد را تعریف کنیم، همانطور که در لیست ۱۰-۱۲ انجام دادیم.
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
Summary
با یک پیادهسازی پیشفرض برای متد summarize
برای استفاده از یک پیادهسازی پیشفرض برای خلاصه کردن نمونههای NewsArticle
، یک بلوک impl
خالی با impl Summary for NewsArticle {}
مشخص میکنیم.
اگرچه دیگر متد summarize
را مستقیماً روی NewsArticle
تعریف نمیکنیم، یک پیادهسازی پیشفرض ارائه دادهایم و مشخص کردهایم که NewsArticle
ویژگی Summary
را پیادهسازی میکند. در نتیجه، همچنان میتوانیم متد summarize
را روی یک نمونه از NewsArticle
فراخوانی کنیم، مانند این:
use aggregator::{self, NewsArticle, Summary};
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win the Stanley Cup Championship!"),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
};
println!("New article available! {}", article.summarize());
}
این کد New article available! (Read more...)
را چاپ میکند.
ایجاد یک پیادهسازی پیشفرض نیازی به تغییر چیزی در پیادهسازی ویژگی Summary
روی Tweet
در لیست ۱۰-۱۳ ندارد. دلیل آن این است که نحو برای بازنویسی یک پیادهسازی پیشفرض همانند نحو برای پیادهسازی یک متد ویژگی است که پیادهسازی پیشفرض ندارد.
پیادهسازیهای پیشفرض میتوانند متدهای دیگر را در همان ویژگی فراخوانی کنند، حتی اگر آن متدهای دیگر پیادهسازی پیشفرض نداشته باشند. به این روش، یک ویژگی میتواند مقدار زیادی عملکرد مفید ارائه دهد و فقط از پیادهسازان بخواهد که بخشی از آن را مشخص کنند. برای مثال، میتوانیم ویژگی Summary
را به گونهای تعریف کنیم که یک متد summarize_author
داشته باشد که پیادهسازی آن الزامی است و سپس یک متد summarize
تعریف کنیم که یک پیادهسازی پیشفرض دارد و متد summarize_author
را فراخوانی میکند:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
برای استفاده از این نسخه از Summary
، فقط باید summarize_author
را هنگامی که ویژگی را روی یک نوع پیادهسازی میکنیم، تعریف کنیم:
pub trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Read more from {}...)", self.summarize_author())
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
بعد از اینکه summarize_author
را تعریف کردیم، میتوانیم متد summarize
را روی نمونههای ساختار Tweet
فراخوانی کنیم، و پیادهسازی پیشفرض summarize
، تعریف متد summarize_author
که ارائه دادهایم را فراخوانی خواهد کرد. از آنجا که ما summarize_author
را پیادهسازی کردهایم، ویژگی Summary
رفتار متد summarize
را بدون نیاز به نوشتن کد اضافی به ما داده است. به این شکل عمل میکند:
use aggregator::{self, Summary, Tweet};
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
};
println!("1 new tweet: {}", tweet.summarize());
}
این کد 1 new tweet: (Read more from @horse_ebooks...)
را چاپ میکند.
توجه داشته باشید که امکان فراخوانی پیادهسازی پیشفرض از یک پیادهسازی بازنویسی شده از همان متد وجود ندارد.
ویژگیها (traits) به عنوان پارامترها
اکنون که میدانید چگونه ویژگیها را تعریف و پیادهسازی کنید، میتوانیم بررسی کنیم که چگونه از ویژگیها برای تعریف توابعی که انواع مختلفی را میپذیرند استفاده کنیم. ما از ویژگی Summary
که روی نوعهای NewsArticle
و Tweet
در لیست ۱۰-۱۳ پیادهسازی کردیم استفاده خواهیم کرد تا تابعی به نام notify
تعریف کنیم که متد summarize
را روی پارامتر item
خود فراخوانی میکند، که از نوعی است که ویژگی Summary
را پیادهسازی میکند. برای این کار، از نحو impl Trait
استفاده میکنیم، مانند این:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
به جای یک نوع مشخص برای پارامتر item
، کلمه کلیدی impl
و نام ویژگی را مشخص میکنیم. این پارامتر هر نوعی را که ویژگی مشخصشده را پیادهسازی میکند میپذیرد. در بدنه notify
، میتوانیم هر متدی روی item
که از ویژگی Summary
آمده باشد، مانند summarize
را فراخوانی کنیم. میتوانیم notify
را فراخوانی کرده و هر نمونهای از NewsArticle
یا Tweet
را به آن پاس دهیم. کدی که تابع را با هر نوع دیگری، مانند یک String
یا یک i32
فراخوانی کند، کامپایل نمیشود زیرا آن نوعها ویژگی Summary
را پیادهسازی نمیکنند.
نحو محدودیت ویژگی (Trait Bound Syntax)
نحو impl Trait
برای موارد ساده مناسب است اما در واقع یک شکل کوتاهشده از یک فرم طولانیتر به نام محدودیت ویژگی (trait bound) است؛ به این صورت:
pub fn notify<T: Summary>(item: &T) {
println!("Breaking news! {}", item.summarize());
}
این فرم طولانی معادل مثال بخش قبلی است اما مفصلتر است. ما محدودیتهای ویژگی را با اعلام پارامتر نوع جنریک بعد از یک دونقطه و داخل پرانتزهای زاویهای قرار میدهیم.
نحو impl Trait
در موارد ساده مناسب است و کد را مختصرتر میکند، در حالی که نحو کاملتر محدودیت ویژگی میتواند پیچیدگی بیشتری را در موارد دیگر بیان کند. برای مثال، میتوانیم دو پارامتر داشته باشیم که ویژگی Summary
را پیادهسازی میکنند. انجام این کار با نحو impl Trait
به این صورت است:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {
استفاده از impl Trait
مناسب است اگر بخواهیم این تابع اجازه دهد item1
و item2
انواع مختلفی داشته باشند (به شرطی که هر دو نوع ویژگی Summary
را پیادهسازی کنند). اما اگر بخواهیم هر دو پارامتر یک نوع یکسان داشته باشند، باید از محدودیت ویژگی استفاده کنیم، مانند این:
pub fn notify<T: Summary>(item1: &T, item2: &T) {
نوع جنریک T
که به عنوان نوع پارامترهای item1
و item2
مشخص شده است، تابع را محدود میکند به این صورت که نوع مشخص مقدار پاسدادهشده به عنوان آرگومان برای item1
و item2
باید یکسان باشد.
مشخص کردن محدودیتهای ویژگی چندگانه با نحو +
ما همچنین میتوانیم بیش از یک محدودیت ویژگی مشخص کنیم. فرض کنید میخواهیم notify
از فرمتبندی نمایش (display formatting) و همچنین summarize
روی item
استفاده کند: در تعریف notify
مشخص میکنیم که item
باید هر دو ویژگی Display
و Summary
را پیادهسازی کند. این کار را میتوانیم با نحو +
انجام دهیم:
pub fn notify(item: &(impl Summary + Display)) {
نحو +
همچنین با محدودیت ویژگی روی انواع جنریک معتبر است:
pub fn notify<T: Summary + Display>(item: &T) {
با مشخص کردن این دو محدودیت ویژگی، بدنه notify
میتواند متد summarize
را فراخوانی کند و از {}
برای فرمتبندی item
استفاده کند.
محدودیتهای ویژگی واضحتر با بندهای where
استفاده از تعداد زیادی محدودیت ویژگی معایب خود را دارد. هر جنریک محدودیتهای ویژگی مخصوص به خود را دارد، بنابراین توابعی با چندین پارامتر نوع جنریک میتوانند شامل اطلاعات زیادی درباره محدودیتهای ویژگی بین نام تابع و لیست پارامترهای آن باشند، که باعث سخت شدن خواندن امضای تابع میشود. به همین دلیل، Rust نحو جایگزینی برای مشخص کردن محدودیتهای ویژگی در داخل یک بند where
پس از امضای تابع ارائه میدهد. بنابراین، به جای نوشتن این:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
میتوانیم از یک بند where
به این صورت استفاده کنیم:
fn some_function<T, U>(t: &T, u: &U) -> i32
where
T: Display + Clone,
U: Clone + Debug,
{
unimplemented!()
}
امضای این تابع کمتر شلوغ است: نام تابع، لیست پارامترها، و نوع بازگشتی به هم نزدیکتر هستند، مشابه یک تابع بدون محدودیتهای ویژگی زیاد.
بازگرداندن نوعهایی که ویژگیها را پیادهسازی میکنند
ما همچنین میتوانیم از نحو impl Trait
در موقعیت بازگشتی استفاده کنیم تا مقداری از نوعی که یک ویژگی را پیادهسازی میکند بازگردانیم، همانطور که در اینجا نشان داده شده است:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
با استفاده از impl Summary
برای نوع بازگشتی، مشخص میکنیم که تابع returns_summarizable
مقداری از نوعی که ویژگی Summary
را پیادهسازی میکند بازمیگرداند، بدون نیاز به نام بردن از نوع مشخص. در این مورد، returns_summarizable
یک Tweet
بازمیگرداند، اما کدی که این تابع را فراخوانی میکند نیازی به دانستن این موضوع ندارد.
توانایی مشخص کردن یک نوع بازگشتی تنها بر اساس ویژگیای که پیادهسازی میکند، به ویژه در زمینه closures و iterators مفید است، که در فصل ۱۳ به آنها میپردازیم. closures و iterators نوعهایی ایجاد میکنند که تنها کامپایلر آنها را میشناسد یا نوعهایی که بسیار طولانی هستند تا مشخص شوند. نحو impl Trait
به شما اجازه میدهد که به طور مختصر مشخص کنید یک تابع نوعی که ویژگی Iterator
را پیادهسازی میکند بازمیگرداند، بدون نیاز به نوشتن یک نوع بسیار طولانی.
با این حال، فقط زمانی میتوانید از impl Trait
استفاده کنید که یک نوع بازگردانده شود. برای مثال، این کد که یا یک NewsArticle
یا یک Tweet
بازمیگرداند و نوع بازگشتی به عنوان impl Summary
مشخص شده، کار نخواهد کرد:
pub trait Summary {
fn summarize(&self) -> String;
}
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
pub struct Tweet {
pub username: String,
pub content: String,
pub reply: bool,
pub retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}
بازگرداندن یا یک NewsArticle
یا یک Tweet
مجاز نیست به دلیل محدودیتهایی در نحوه پیادهسازی نحو impl Trait
در کامپایلر. ما نحوه نوشتن یک تابع با این رفتار را در بخش “استفاده از اشیاء ویژگی که مقادیر از نوعهای مختلف را مجاز میسازد” در فصل ۱۸ بررسی خواهیم کرد.
استفاده از محدودیتهای ویژگی برای پیادهسازی شرطی متدها
با استفاده از یک محدودیت ویژگی در یک بلوک impl
که از پارامترهای نوع جنریک استفاده میکند، میتوانیم متدها را به طور شرطی برای نوعهایی که ویژگیهای مشخصشده را پیادهسازی میکنند پیادهسازی کنیم. برای مثال، نوع Pair<T>
در لیست ۱۰-۱۵ همیشه تابع new
را پیادهسازی میکند تا یک نمونه جدید از Pair<T>
بازگرداند (به یاد داشته باشید از بخش “تعریف متدها” در فصل ۵ که Self
یک نام مستعار برای نوع بلوک impl
است که در اینجا Pair<T>
است). اما در بلوک impl
بعدی، Pair<T>
فقط متد cmp_display
را پیادهسازی میکند اگر نوع داخلی T
ویژگی PartialOrd
که مقایسه را ممکن میکند و ویژگی Display
که چاپ را ممکن میکند، پیادهسازی کند.
use std::fmt::Display;
struct Pair<T> {
x: T,
y: T,
}
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<T: Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.x >= self.y {
println!("The largest member is x = {}", self.x);
} else {
println!("The largest member is y = {}", self.y);
}
}
}
ما همچنین میتوانیم یک ویژگی را به طور شرطی برای هر نوعی که ویژگی دیگری را پیادهسازی میکند، پیادهسازی کنیم. پیادهسازیهای یک ویژگی روی هر نوعی که محدودیتهای ویژگی را برآورده میکند پیادهسازیهای کلی (blanket implementations) نامیده میشوند و به طور گسترده در کتابخانه استاندارد Rust استفاده میشوند. برای مثال، کتابخانه استاندارد ویژگی ToString
را روی هر نوعی که ویژگی Display
را پیادهسازی میکند، پیادهسازی میکند. بلوک impl
در کتابخانه استاندارد شبیه به این کد است:
impl<T: Display> ToString for T {
// --snip--
}
از آنجا که کتابخانه استاندارد این پیادهسازی کلی را دارد، میتوانیم متد to_string
تعریفشده توسط ویژگی ToString
را روی هر نوعی که ویژگی Display
را پیادهسازی میکند، فراخوانی کنیم. برای مثال، میتوانیم اعداد صحیح را به مقادیر String
متناظرشان تبدیل کنیم مانند این:
#![allow(unused)] fn main() { let s = 3.to_string(); }
پیادهسازیهای کلی در مستندات ویژگی در بخش “Implementors” ظاهر میشوند.
ویژگیها و محدودیتهای ویژگی به ما امکان میدهند که کدی بنویسیم که از پارامترهای نوع جنریک برای کاهش تکرار استفاده کند اما همچنین به کامپایلر مشخص کند که میخواهیم نوع جنریک رفتار خاصی داشته باشد. سپس کامپایلر میتواند از اطلاعات محدودیت ویژگی استفاده کند تا بررسی کند که تمام نوعهای مشخص استفادهشده با کد ما رفتار صحیح را ارائه میدهند. در زبانهای تایپگذاری پویا، ما هنگام اجرا خطا دریافت میکنیم اگر یک متد روی یک نوع که آن متد را تعریف نکرده فراخوانی کنیم. اما Rust این خطاها را به زمان کامپایل منتقل میکند تا ما مجبور شویم مشکلات را قبل از اینکه کد ما اجرا شود برطرف کنیم. علاوه بر این، نیازی به نوشتن کدی نداریم که رفتار را در زمان اجرا بررسی کند زیرا قبلاً آن را در زمان کامپایل بررسی کردهایم. این کار عملکرد را بهبود میبخشد بدون اینکه انعطافپذیری جنریکها را قربانی کند.