0Pricing
Learn Rust Coding · درس

Builders بحالة النوع

شفّر الصلاحية في نظام الأنواع

Builders بحالة النوع درس مجاني في Learn Rust Coding على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Learn Rust Coding، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

مشكلة Builders العادية

يسمح Builder العادي باستدعاء build() في أي وقت، حتى قبل تعيين الحقول المطلوبة. وعندها تظهر البيانات المفقودة على شكل حالة ذعر أثناء وقت التشغيل أو قيمة Err.

تنقل Builders ذات حالة النوع هذا التحقق إلى وقت الترجمة؛ فنسيان خطوة مطلوبة يؤدي ببساطة إلى فشل الترجمة.

ترميز الحالة في الأنواع

تتمثل الحيلة في جعل Builder عامًا بالنسبة إلى أنواع مميِّزة تمثل الخطوات المنجزة. وعند تعيين كل حقل، يتغير نوع Builder.

ولا تصبح دالة build() متاحة إلا عندما تصل كل علامة مطلوبة إلى حالة "set" الخاصة بها.

struct Missing;
struct Set;

Builder عام بالنسبة إلى العلامات

امنحوا نوع Builder معاملات نوعية لحالة كل حقل مطلوب. وتحمل PhantomData العلامة من دون تخزين أي بيانات فعلية.

use std::marker::PhantomData;
struct ReqBuilder<H, U> {
    url: Option<String>,
    method: Option<String>,
    _state: PhantomData<(H, U)>,
}

الحالة الابتدائية

يعيد المُنشئ Builder تكون فيه كل علامة مطلوبة هي Missing. وفي هذه المرحلة لا توجد build()، ولذلك يعرف نظام الأنواع أن الكائن غير مكتمل.

impl ReqBuilder<Missing, Missing> {
    fn new() -> Self {
        ReqBuilder { url: None, method: None, _state: PhantomData }
    }
}

نقل علامة

تستهلك دالة الإسناد Builder القديم وتعيد Builder جديدًا تتحول فيه علامة ذلك الحقل إلى Set. وتبقى العلامة الأخرى محفوظةً عبر إبقاء معامل نوعها عامًا.

impl<U> ReqBuilder<Missing, U> {
    fn url(self, url: &str) -> ReqBuilder<Set, U> {
        ReqBuilder { url: Some(url.to_string()),
            method: self.method, _state: PhantomData }
    }
}

النقل الثاني

يعمل تعيين الدالة بالطريقة نفسها، إذ يحوّل العلامة الثانية من Missing إلى Set مع إبقاء الأولى دون تغيير.

impl<H> ReqBuilder<H, Missing> {
    fn method(self, m: &str) -> ReqBuilder<H, Set> {
        ReqBuilder { url: self.url,
            method: Some(m.to_string()), _state: PhantomData }
    }
}

لا تتاح build() إلا عند الاكتمال

والأهم أن build() مطبّقة فقط من أجل ReqBuilder<Set, Set>. أما أي حالة أخرى فلا تحتوي على هذه الدالة، ولذلك يفشل الاستدعاء في الترجمة.

ولا يمكن لاستدعاءات unwrap داخلها أن تسبب حالة ذعر، لأن النوع يثبت وجود الحقلين.

struct Request { url: String, method: String }
impl ReqBuilder<Set, Set> {
    fn build(self) -> Request {
        Request { url: self.url.unwrap(), method: self.method.unwrap() }
    }
}

جمع الأجزاء

تُترجم السلسلة الصحيحة بنجاح، لأن كل استدعاء ينقل Builder نحو ReqBuilder<Set, Set> حيث توجد build().

fn demo() -> Request {
    ReqBuilder::new()
        .url("https://example.com")
        .method("GET")
        .build()
}

خطأ الترجمة الذي نريده

تجاوزوا خطوة مطلوبة، وسيرفض المصرّف ذلك. إذ يؤدي استدعاء build() على ReqBuilder<Set, Missing> إلى ظهور الرسالة "no method named build"، فيكتشف الإغفال قبل تشغيل البرنامج.

// ReqBuilder::new().url("x").build();
// error: no method named `build` found for
// ReqBuilder<Set, Missing>

نسخة كاملة قابلة للتشغيل

يستخدم هذا البرنامج المختصر حقلًا مطلوبًا واحدًا لعرض عملية الانتقال كاملةً من البداية إلى النهاية. ويُترجم ويطبع القيمة المُنشأة.

use std::marker::PhantomData;
struct Missing; struct Set;
struct B<N> { name: Option<String>, _s: PhantomData<N> }
impl B<Missing> {
    fn new() -> Self { B { name: None, _s: PhantomData } }
    fn name(self, n: &str) -> B<Set> {
        B { name: Some(n.to_string()), _s: PhantomData }
    }
}
impl B<Set> {
    fn build(self) -> String { self.name.unwrap() }
}
fn main() {
    let v = B::new().name("prod").build();
    println!("built: {}", v);
}

التكاليف والمفاضلات

توفر Builders ذات حالة النوع ضمانات وقت الترجمة من دون تكلفة وقت تشغيل، لأن العلامات تُزال. لكن الثمن هو زيادة تعقيد الأنواع وكتل impl المتعددة مع ازدياد الحقول المطلوبة.

احصروا استخدام هذا النمط في واجهات API التي يجب أن يستحيل فيها إنشاء قيمة غير مكتملة بحكم التصميم.

تحقق سريع

حدّدوا ما الذي يفرض اكتمال Builder ذات حالة النوع فعليًا.

مراجعة

ترمّز Builders ذات حالة النوع كل خطوة مطلوبة في معامل نوع مميِّز تحمله PhantomData. وتستهلك دوال الإسناد Builder وتعيد نوعًا جديدًا تحولت فيه إحدى العلامات إلى Set.

وبما أن build() مطبّقة فقط في حالة Set الكاملة، فإن نسيان أي خطوة يصبح خطأ ترجمة لا حالة ذعر أثناء وقت التشغيل، وكل ذلك من دون تكلفة وقت تشغيل.

الأسئلة الشائعة

هل درس «Builders بحالة النوع» مجاني؟

نعم — نص درس «Builders بحالة النوع» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Learn Rust Coding، انتقل إلى CoddyKit PRO. تتضمن دورة Learn Rust Coding 4 دروس في المجموع.

ماذا ستتعلم في «Builders بحالة النوع»؟

شفّر الصلاحية في نظام الأنواع تتمرن على Learn Rust Coding مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Learn Rust Coding؟

لا تُشترط خبرة سابقة. Learn Rust Coding على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «Builders بحالة النوع»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Learn Rust Coding هذا؟

نعم. كل درس في Learn Rust Coding يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. نمط Builder
  2. نمط Newtype
  3. Builders بحالة النوع
  4. Deref وسهولة استخدام الأغلفة
← العودة إلى Learn Rust Coding