مقدمه
در فصل ۱۰ با مفهوم Trait به عنوان روشی برای تعریف رفتارهای مشترک آشنا شدیم. در این درس، به
بررسی چند قابلیت پیشرفتهتر در سیستم trait میپردازیم که به ما اجازه میدهند تا انتزاعهای
قدرتمندتر و انعطافپذیرتری بسازیم. این قابلیتها شامل «نوعهای وابسته» (Associated Types) و
«الگوی نوع جدید» (Newtype Pattern) میشوند.
نوعهای وابسته (Associated Types)
«نوعهای وابسته» یک نوع
placeholder را به یک trait متصل میکنند. این کار به ما اجازه میدهد
تا در تعریف یک trait، از نوعهایی استفاده کنیم که تا زمان پیادهسازی آن trait برای یک نوع
مشخص، تعریف نخواهند شد. این مفهوم شبیه به نوعهای جنریک است، اما یک تفاوت کلیدی دارد: یک نوع
تنها میتواند یک trait را با یک مجموعه مشخص از نوعهای وابسته پیادهسازی کند، در حالی که
میتواند یک trait جنریک را برای چندین نوع مختلف پیادهسازی کند.
یک مثال عالی از این مفهوم، trait مربوط به Iterator در کتابخانه استاندارد است:
RUST
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
در اینجا، Item یک نوع وابسته است. هر نوعی که Iterator را پیادهسازی میکند، باید نوع
Item را نیز مشخص کند. برای مثال، در پیادهسازی Iterator برای وکتور Vec<i32>، نوع Item
برابر با &i32 خواهد بود.
الگوی نوع جدید (Newtype Pattern)
در Rust، یک قانون سختگیرانه وجود دارد که به آن «قانون یتیمی» (orphan rule) میگویند: شما تنها
میتوانید یک trait را برای یک نوع پیادهسازی کنید، اگر حداقل یکی از آن دو (trait یا نوع) در
crate محلی شما تعریف شده باشد. این قانون به شما اجازه نمیدهد تا یک trait خارجی (مانند
Display از کتابخانه استاندارد) را برای یک نوع خارجی (مانند Vec<T>) پیادهسازی کنید.
برای دور زدن این محدودیت، از «الگوی نوع جدید» یا Newtype Pattern استفاده میکنیم. در این
الگو، ما یک struct تاپل با یک عضو تعریف میکنیم که نوع خارجی مورد نظر ما را در خود میپوشاند
(wrap میکند). از آنجایی که این struct جدید در crate محلی ما قرار دارد، ما میتوانیم هر
trait خارجی را برای آن پیادهسازی کنیم.
src/main.rs
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}
در این مثال، با ایجاد Wrapper، توانستیم Display را برای نوعی که در اصل یک وکتور از
رشتههاست، پیادهسازی کنیم. Newtype Pattern هیچ هزینه عملکردی در زمان اجرا ندارد، زیرا
کامپایلر Rust در زمان کامپایل این پوشش را بهینه و حذف میکند.
خلاصه
در این درس با دو مفهوم پیشرفته در سیستم trait آشنا شدیم. «نوعهای وابسته» به ما اجازه
میدهند تا traitهای انعطافپذیرتری بنویسیم و «الگوی نوع جدید» راهی برای پیادهسازی traitهای
خارجی روی نوعهای خارجی فراهم میکند. این ابزارها قدرت بیان و انتزاع را در زبان Rust به شکل قابل
توجهی افزایش میدهند. در درس بعدی، به بررسی «نوعهای پیشرفته» خواهیم پرداخت و با مفاهیمی مانند
type alias و never type آشنا میشویم.