مقدمه
سیستم نوع (type system) در Rust بسیار قدرتمند و گویا است. در این درس به بررسی چند مفهوم پیشرفته
در این سیستم میپردازیم که به ما در نوشتن کدهای خواناتر و مدیریت موارد خاص کمک میکنند. این
مفاهیم شامل مترادفهای نوع (type aliases)، نوع never و نوعهای با سایز دینامیک (Dynamically
Sized Types) میشوند.
ایجاد مترادف نوع با کلمه کلیدی type
در Rust، شما میتوانید با استفاده از کلمه کلیدی type، یک نام مترادف یا «مستعار» (alias)
برای یک نوع دیگر ایجاد کنید. این کار به شما اجازه میدهد تا با دادن یک نام کوتاهتر و معنادارتر
به یک نوع طولانی و تکراری، خوانایی کد خود را افزایش دهید.
یک کاربرد بسیار رایج برای این قابلیت، سادهسازی نوع Result<T, E> است. اگر در چندین جای کد خود
از یک نوع Result یکسان استفاده میکنید، میتوانید یک مترادف برای آن بسازید.
src/lib.rs
use std::fmt;
use std::io::Error;
type Result<T> = std::result::Result<T, Error>;
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Result<usize>;
fn flush(&mut self) -> Result<()>;
}
در اینجا، ما یک مترادف به نام Result<T> برای std::result::Result<T, std::io::Error> ایجاد
کردهایم. این کار باعث میشود که امضای متدهای Write بسیار کوتاهتر و خواناتر
شود، بدون اینکه هیچ هزینه عملکردی در زمان اجرا داشته باشد. این تنها یک جایگزینی نام در
زمان کامپایل است.
نوع Never که هرگز برنمیگردد: !
Rust یک نوع خاص به نام never type دارد که با علامت ! نمایش داده میشود. این نوع،
نمایندهی محاسباتی است که هرگز به پایان نمیرسند و مقداری را برنمیگردانند. برای مثال، تابعی که
برنامه را با panic! متوقف میکند یا یک تابع که در یک حلقه بینهایت گیر میکند، از این نوع
هستند.
src/main.rs
fn bar() -> ! {
panic!("This function never returns");
}
این نوع در عبارات match نیز کاربرد دارد. اگر یک شاخه match باعث panic یا خروج از برنامه
شود، کامپایلر نوع آن شاخه را `!` در نظر میگیرد و میتواند آن را به هر نوع دیگری تبدیل کند تا
نوع کلی عبارت match معتبر باقی بماند.
نوعهای با سایز دینامیک (Dynamically Sized Types)
Rust نیاز دارد که سایز هر نوع را در زمان کامپایل بداند. اما یک دسته از نوعها به نام «نوعهای با
سایز دینامیک» یا DSTها (که به آنها unsized types نیز گفته میشود) وجود دارند که سایز آنها
تنها در زمان اجرا مشخص میشود. نوع str (نه &str) یک مثال از این نوعهاست. ما نمیتوانیم یک
متغیر از نوع str بسازیم، زیرا کامپایلر نمیداند چقدر حافظه باید برای آن تخصیص دهد.
قانون کلی این است: ما همیشه باید مقادیر از نوع DST را پشت یک نوع اشارهگر (مانند &، Box،
یا Rc) قرار دهیم. یک اشارهگر به یک DST، دو بخش اطلاعات را در خود ذخیره میکند: آدرس داده، و
متادیتای مربوط به سایز آن (مثلاً طول یک &str). به همین دلیل به آنها "fat pointers" یا
اشارهگرهای چاق میگویند.
trait مربوط به Sized به صورت خودکار برای تمام نوعهایی که سایزشان در زمان کامپایل مشخص است،
پیادهسازی میشود. Rust به صورت ضمنی یک trait bound از نوع Sized را به تمام پارامترهای جنریک
اضافه میکند. برای کار با DSTها در توابع جنریک، باید این محدودیت را با سینتکس خاص ?Sized حذف
کنیم.
در این درس با چند مفهوم پیشرفته در سیستم نوع Rust آشنا شدیم. دیدیم که چگونه میتوان با type
aliasها کد را خواناتر کرد، چگونه نوع never برای نمایش محاسبات بیپایان به کار میرود، و چرا
باید با نوعهای DST از طریق اشارهگرها کار کنیم. در درس بعدی، به بررسی «توابع و کلوژرهای
پیشرفته» خواهیم پرداخت و با مفاهیمی مانند اشارهگرهای تابع و کلوژرهای بازگشتی آشنا خواهیم شد.