🦀 برسی کدهای اضافه شده Rust :
یکی از قسمتهای جالب این RFC، نحوه طراحی APIها در Rust است. تقریباً تمام بخشهای Unsafe فقط در مرز ارتباط با کدهای C قرار گرفتهاند و منطق Driver کاملاً با Safe Rust نوشته شده است.
۱- Wrapper ایمن روی APIهای C
به جای اینکه Driver مستقیماً تابع C زیر را فراخوانی کند:
i2c_smbus_read_byte_data(...)
یک Wrapper امن در I2cClient اضافه شده است:
pub fn smbus_read_byte_data(&self, command: u8) -> Result<u8> {
let ret = unsafe {
bindings::i2c_smbus_read_byte_data(self.as_raw(), command)
};
if ret < 0 {
Err(Error::from_errno(ret))
} else {
Ok(ret as u8)
}
}
در اینجا تنها بخش Unsafe همان فراخوانی تابع C است. بعد از آن، مقدار بازگشتی به یک Result<u8> استاندارد Rust تبدیل میشود و Driver دیگر نیازی به بررسی کدهای خطای C ندارد.
۲- پیادهسازی الگوی معروف Read → Modify → Write
یکی از متدهای بسیار کاربردی که اضافه شده:
pub fn smbus_update_bits(...)
درون آن ابتدا مقدار رجیستر خوانده میشود:
let old = self.smbus_read_byte_data(command)?;
سپس فقط بیتهای موردنظر تغییر میکنند:
let new = (old & !mask) | (value & mask);
و تنها در صورتی که مقدار واقعاً تغییر کرده باشد:
if new != old {
self.smbus_write_byte_data(command, new)?;
}
نوشتن روی سختافزار انجام میشود.
این دقیقاً همان الگویی است که تقریباً در اکثر Driverهای C لینوکس نیز دیده میشود.
۳- طراحی Driver با Trait
به جای استفاده از Structهای متعدد و Callbackهای C، تنها کافی است Driver این Trait را پیادهسازی کند:
pub trait Driver {
const NAME: &'static CStr;
const TYPE: Type;
const PROPERTIES: &'static [Property];
fn get_property(...)
}
این طراحی باعث میشود هر Driver فقط روی منطق خود تمرکز کند و تمام جزئیات مربوط به Power Supply Core در پشت Abstraction پنهان بماند.
۴- استفاده از RAII برای مدیریت Driver
در Rust یک Struct به نام Registration معرفی شده است.
نکته جالب این است که هنگام از بین رفتن این شیء:
impl Drop for Registration {
fn drop(&mut self) {
unsafe {
bindings::power_supply_unregister(self.psy);
}
}
}
تابع power_supply_unregister() به صورت خودکار اجرا میشود.
در نتیجه توسعهدهنده دیگر نگران آزادسازی منابع یا فراموش کردن Unregister کردن Driver نخواهد بود؛ این کار به کمک مکانیزم Drop در Rust انجام میشود.
۵- تعریف Driver واقعی فقط در چند خط
در Driver مربوط به SMB347 تنها با چند ثابت میتوان مشخص کرد که Driver چه قابلیتهایی دارد:
const NAME: &'static CStr = c"smb347-mains";
const PROPERTIES: &'static [Property] = &[
PROP_STATUS,
PROP_ONLINE,
PROP_CHARGE_TYPE,
];
و سپس تنها تابعی که باید پیادهسازی شود:
fn get_property(...)
که وضعیت شارژر را از رجیسترهای سختافزار میخواند و آن را به Power Supply Framework گزارش میدهد.
این طراحی باعث شده حجم زیادی از کدهای تکراری Driverهای C حذف شوند و توسعه Driver بیشتر شبیه پیادهسازی یک Trait در Rust باشد تا کار با Callbackها و Pointerهای متعدد.
۶- نکتهای که توجه Maintainerها را جلب کرد
یکی از جالبترین قسمتهای Review این RFC مربوط به همین چند خط بود:
pub fn register<T: Driver + 'static>(dev: &Device)
Alice Ryhl پیشنهاد داد که به جای &Device از &Device<Bound> استفاده شود تا از نظر Type System تضمین شود که Driver فقط روی Deviceهایی که کاملاً Bind شدهاند ثبت میشود.
همچنین پیشنهاد کرد این تابع به جای یک تابع آزاد، به شکل:
Registration::new(...)
بازطراحی شود تا با سایر APIهای Rust Kernel هماهنگ باشد.
این بازخوردها نشان میدهد که در پروژه Rust for Linux، علاوه بر عملکرد، طراحی API و سازگاری با الگوهای Rust نیز اهمیت بسیار زیادی دارد.
@Amin_Techlab