Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

تعريف السلوك المشترك باستخدام السمات (Defining Shared Behavior with Traits)

تحدد الـ سمة (trait) الوظائف التي يمتلكها نوع معين ويمكنه مشاركتها مع أنواع أخرى. يمكننا استخدام الـ traits لتعريف السلوك المشترك بطريقة مجردة (abstract). كما يمكننا استخدام قيود السمات (trait bounds) لتحديد أن النوع العام (generic type) يمكن أن يكون أي نوع يمتلك سلوكًا معينًا.

ملاحظة: الـ traits تشبه ميزة تسمى غالبًا الواجهات (interfaces) في لغات أخرى، وإن كان مع بعض الاختلافات.

تعريف سمة (Defining a Trait)

يتكون سلوك النوع من الدوال (methods) التي يمكننا استدعاؤها على ذلك النوع. تتشارك الأنواع المختلفة في نفس السلوك إذا كان بإمكاننا استدعاء نفس الـ methods على كل تلك الأنواع. تعريفات الـ Trait هي وسيلة لتجميع تواقيع الدوال (method signatures) معًا لتعريف مجموعة من السلوكيات الضرورية لتحقيق غرض ما.

على سبيل المثال، لنفترض أن لدينا عدة هياكل (structs) تحمل أنواعًا وكميات مختلفة من النصوص: هيكل NewsArticle يحمل قصة إخبارية مسجلة في موقع معين، وهيكل SocialPost يمكن أن يحتوي على 280 حرفًا كحد أقصى مع بيانات وصفية (metadata) تشير إلى ما إذا كان منشورًا جديدًا، أو إعادة نشر، أو ردًا على منشور آخر.

نريد إنشاء حزمة مكتبة (library crate) لمجمع وسائط تسمى aggregator يمكنها عرض ملخصات للبيانات التي قد تكون مخزنة في مثيل (instance) من NewsArticle أو SocialPost. للقيام بذلك، نحتاج إلى ملخص من كل نوع، وسنطلب ذلك الملخص عن طريق استدعاء دالة summarize على الـ instance. توضح القائمة 10-12 تعريف سمة Summary عامة (public) تعبر عن هذا السلوك.

pub trait Summary {
    fn summarize(&self) -> String;
}

هنا، نعلن عن trait باستخدام الكلمة المفتاحية trait ثم اسم الـ trait، وهو Summary في هذه الحالة. نعلن أيضًا عن الـ trait كـ pub بحيث يمكن للـ crates التي تعتمد على هذه الـ crate استخدام هذه الـ trait أيضًا، كما سنرى في بضعة أمثلة. داخل الأقواس المتعرجة، نعلن عن الـ method signatures التي تصف سلوكيات الأنواع التي تنفذ (implement) هذه الـ trait، وهي في هذه الحالة fn summarize(&self) -> String.

بعد الـ method signature، وبدلاً من توفير تنفيذ (implementation) داخل أقواس متعرجة، نستخدم فاصلة منقوطة. يجب على كل نوع ينفذ هذه الـ trait توفير سلوكه المخصص لجسم الـ method. سيفرض المترجم (compiler) أن أي نوع يمتلك الـ Summary trait سيكون لديه الـ method المسمى summarize معرفًا بهذا التوقيع (signature) تمامًا.

يمكن أن يحتوي الـ trait على عدة methods في جسمه: يتم إدراج الـ method signatures واحدًا تلو الآخر في كل سطر، وينتهي كل سطر بفاصلة منقوطة.

تنفيذ سمة على نوع (Implementing a Trait on a Type)

الآن بعد أن حددنا التواقيع المطلوبة لـ methods سمة Summary يمكننا تنفيذها على الأنواع في مجمع الوسائط الخاص بنا. توضح القائمة 10-13 تنفيذًا لـ Summary trait على هيكل NewsArticle يستخدم العنوان الرئيسي، والمؤلف، والموقع لإنشاء القيمة المعادة من summarize. بالنسبة لهيكل SocialPost نحدد summarize كاسم المستخدم متبوعًا بالنص الكامل للمنشور، بافتراض أن محتوى المنشور محدود بالفعل بـ 280 حرفًا.

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

تنفيذ trait على نوع يشبه تنفيذ الـ methods العادية. الفرق هو أنه بعد impl نضع اسم الـ trait الذي نريد تنفيذه، ثم نستخدم الكلمة المفتاحية for ثم نحدد اسم النوع الذي نريد تنفيذ الـ trait له. داخل كتلة impl نضع الـ method signatures التي حددها تعريف الـ trait. وبدلاً من إضافة فاصلة منقوطة بعد كل signature، نستخدم الأقواس المتعرجة ونملأ جسم الـ method بالسلوك المحدد الذي نريده لـ methods الـ trait لهذا النوع المعين.

الآن بعد أن نفذت المكتبة الـ Summary trait على NewsArticle و SocialPost يمكن لمستخدمي الـ crate استدعاء الـ trait methods على instances من NewsArticle و SocialPost بنفس الطريقة التي نستدعي بها الـ methods العادية. الفرق الوحيد هو أنه يجب على المستخدم إحضار الـ trait إلى النطاق (scope) بالإضافة إلى الأنواع. إليك مثال على كيفية استخدام binary crate لمكتبة aggregator الخاصة بنا:

use aggregator::{SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

يطبع هذا الكود 1 new post: horse_ebooks: of course, as you probably already know, people.

يمكن للـ crates الأخرى التي تعتمد على aggregator crate أيضًا إحضار الـ Summary trait إلى الـ scope لتنفيذ Summary على أنواعها الخاصة. أحد القيود التي يجب ملاحظتها هو أنه يمكننا تنفيذ trait على نوع فقط إذا كان الـ trait أو النوع، أو كلاهما، محليًا (local) للـ crate الخاصة بنا. على سبيل المثال، يمكننا تنفيذ traits المكتبة القياسية (standard library) مثل Display على نوع مخصص مثل SocialPost كجزء من وظائف aggregator crate لأن النوع SocialPost محلي لـ aggregator crate الخاصة بنا. يمكننا أيضًا تنفيذ Summary على Vec<T> في aggregator crate لأن الـ trait المسمى Summary محلي لـ aggregator crate الخاصة بنا.

لكن لا يمكننا تنفيذ traits خارجية على أنواع خارجية. على سبيل المثال، لا يمكننا تنفيذ الـ Display trait على Vec<T> داخل aggregator crate الخاصة بنا، لأن Display و Vec<T> كلاهما معرفان في المكتبة القياسية وليسا محليين لـ aggregator crate الخاصة بنا. هذا القيد هو جزء من خاصية تسمى التماسك (coherence)، وبشكل أكثر تحديدًا قاعدة اليتيم (orphan rule)، وسميت بهذا الاسم لأن النوع الأب غير موجود. تضمن هذه القاعدة أن كود الأشخاص الآخرين لا يمكنه كسر كودك والعكس صحيح. بدون هذه القاعدة، يمكن لـ crates اثنتين تنفيذ نفس الـ trait لنفس النوع، ولن يعرف Rust أي تنفيذ يستخدم.

استخدام التنفيذات الافتراضية (Using Default Implementations)

أحيانًا يكون من المفيد الحصول على سلوك افتراضي (default behavior) لبعض أو كل الـ methods في trait بدلاً من طلب تنفيذات لجميع الـ methods على كل نوع. بعد ذلك، عندما ننفذ الـ trait على نوع معين، يمكننا الاحتفاظ بالسلوك الافتراضي لكل method أو تجاوزه (override).

في القائمة 10-14، نحدد سلسلة نصية افتراضية لدالة summarize في سمة Summary بدلاً من الاكتفاء بتعريف الـ method signature، كما فعلنا في القائمة 10-12.

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

لاستخدام تنفيذ افتراضي لتلخيص instances من NewsArticle نحدد كتلة impl فارغة باستخدام impl Summary for NewsArticle {}.

على الرغم من أننا لم نعد نعرف دالة summarize على NewsArticle مباشرة، فقد قدمنا تنفيذًا افتراضيًا وحددنا أن NewsArticle ينفذ الـ Summary trait. ونتيجة لذلك، لا يزال بإمكاننا استدعاء دالة summarize على instance من 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 على SocialPost في القائمة 10-13. والسبب هو أن بناء الجملة (syntax) لتجاوز تنفيذ افتراضي هو نفسه الـ syntax لتنفيذ trait method ليس له تنفيذ افتراضي.

يمكن للتنفيذات الافتراضية استدعاء methods أخرى في نفس الـ trait، حتى لو لم يكن لتلك الـ methods الأخرى تنفيذ افتراضي. بهذه الطريقة، يمكن لـ trait توفير الكثير من الوظائف المفيدة وتتطلب فقط من المنفذين تحديد جزء صغير منها. على سبيل المثال، يمكننا تعريف الـ Summary trait ليكون له دالة 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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

لاستخدام هذه النسخة من Summary نحتاج فقط إلى تعريف summarize_author عندما ننفذ الـ trait على نوع ما:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}

بعد تعريف summarize_author يمكننا استدعاء summarize على instances من هيكل SocialPost وسيقوم التنفيذ الافتراضي لـ summarize باستدعاء تعريف summarize_author الذي قدمناه. نظرًا لأننا نفذنا summarize_author فقد منحتنا الـ Summary trait سلوك دالة summarize دون مطالبتنا بكتابة أي كود إضافي. إليك كيف يبدو ذلك:

use aggregator::{self, SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

يطبع هذا الكود 1 new post: (Read more from @horse_ebooks...).

لاحظ أنه ليس من الممكن استدعاء التنفيذ الافتراضي من تنفيذ متجاوز (overriding implementation) لنفس الـ method.

استخدام السمات كمعاملات (Using Traits as Parameters)

الآن بعد أن عرفت كيفية تعريف وتنفيذ الـ traits، يمكننا استكشاف كيفية استخدام الـ traits لتعريف دوال تقبل العديد من الأنواع المختلفة. سنستخدم الـ Summary trait الذي نفذناه على نوعي NewsArticle و SocialPost في القائمة 10-13 لتعريف دالة notify تستدعي دالة summarize على معاملها (parameter) المسمى item وهو من نوع ما ينفذ الـ Summary trait. للقيام بذلك، نستخدم الـ impl Trait syntax بهذا الشكل:

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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

بدلاً من نوع ملموس (concrete type) للمعامل item نحدد الكلمة المفتاحية impl واسم الـ trait. يقبل هذا الـ parameter أي نوع ينفذ الـ trait المحدد. في جسم notify يمكننا استدعاء أي methods على item تأتي من الـ Summary trait، مثل summarize. يمكننا استدعاء notify وتمرير أي instance من NewsArticle أو SocialPost. الكود الذي يستدعي الدالة مع أي نوع آخر، مثل String أو i32 لن يترجم، لأن تلك الأنواع لا تنفذ Summary.

بناء جملة قيد السمة (Trait Bound Syntax)

يعمل الـ impl Trait syntax في الحالات المباشرة ولكنه في الواقع عبارة عن اختصار برمجي (syntax sugar) لشكل أطول يعرف باسم قيد السمة (trait bound)؛ ويبدو هكذا:

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

هذا الشكل الأطول مكافئ للمثال في القسم السابق ولكنه أكثر تفصيلاً. نضع الـ trait bounds مع الإعلان عن معامل النوع العام (generic type parameter) بعد نقطتين وداخل أقواس زاوية.

يعتبر الـ trait bound syntax مناسبًا للحالات الأكثر تعقيدًا. على سبيل المثال، يمكن أن يكون لدينا معاملان ينفذان Summary. القيام بذلك باستخدام الـ impl Trait syntax يبدو هكذا:

pub fn notify(item1: &impl Summary, item2: &impl Summary) {

استخدام impl Trait مناسب إذا أردنا أن تسمح هذه الدالة لـ item1 و item2 بأن يكون لهما نوعان مختلفان (طالما أن كلا النوعين ينفذان Summary). أما إذا أردنا إجبار كلا المعاملين على أن يكون لهما نفس النوع، فيجب علينا استخدام trait bound بهذا الشكل:

pub fn notify<T: Summary>(item1: &T, item2: &T) {

النوع العام T المحدد كنوع للمعاملين item1 و item2 يقيد الدالة بحيث يجب أن يكون النوع الملموس للقيمة الممررة كوسيط (argument) لـ item1 و item2 هو نفسه.

قيود سمات متعددة باستخدام الـ + Syntax (Multiple Trait Bounds with the + Syntax)

يمكننا أيضًا تحديد أكثر من trait bound واحد. لنفترض أننا أردنا أن تستخدم notify تنسيق العرض (display formatting) بالإضافة إلى summarize على item: نحدد في تعريف notify أن item يجب أن ينفذ كلاً من Display و Summary. يمكننا القيام بذلك باستخدام الـ + syntax:

pub fn notify(item: &(impl Summary + Display)) {

الـ + syntax صالح أيضًا مع الـ trait bounds على الأنواع العامة:

pub fn notify<T: Summary + Display>(item: &T) {

مع تحديد قيدي السمة، يمكن لجسم notify استدعاء summarize واستخدام {} لتنسيق item.

قيود سمات أكثر وضوحًا باستخدام بنود where (Clearer Trait Bounds with where Clauses)

استخدام الكثير من الـ trait bounds له سلبياته. فكل نوع عام له الـ trait bounds الخاصة به، لذا فإن الدوال التي تحتوي على عدة معاملات أنواع عامة يمكن أن تحتوي على الكثير من معلومات الـ trait bound بين اسم الدالة وقائمة معاملاتها، مما يجعل توقيع الدالة (function signature) صعب القراءة. لهذا السبب، يمتلك Rust بناء جملة بديلاً لتحديد الـ trait bounds داخل بند where بعد function signature. لذا، بدلاً من كتابة هذا:

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!()
}

توقيع هذه الدالة أقل فوضى: اسم الدالة، وقائمة المعاملات، ونوع الإرجاع قريبة من بعضها البعض، بشكل مشابه لدالة لا تحتوي على الكثير من الـ trait bounds.

إرجاع الأنواع التي تنفذ السمات (Returning Types That Implement Traits)

يمكننا أيضًا استخدام الـ impl Trait syntax في موضع الإرجاع لإعادة قيمة من نوع ما ينفذ 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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn returns_summarizable() -> impl Summary {
    SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    }
}

باستخدام impl Summary لنوع الإرجاع، نحدد أن دالة returns_summarizable تعيد نوعًا ما ينفذ الـ Summary trait دون تسمية النوع الملموس. في هذه الحالة، تعيد returns_summarizable هيكل SocialPost ولكن الكود الذي يستدعي هذه الدالة لا يحتاج إلى معرفة ذلك.

القدرة على تحديد نوع الإرجاع فقط من خلال الـ trait الذي ينفذه مفيدة بشكل خاص في سياق الـ closures والـ iterators، والتي نغطيها في الفصل 13. تنشئ الـ closures والـ iterators أنواعًا لا يعرفها إلا المترجم أو أنواعًا طويلة جدًا في تحديدها. يتيح لك الـ impl Trait syntax تحديد أن الدالة تعيد نوعًا ما ينفذ الـ Iterator trait بإيجاز دون الحاجة إلى كتابة نوع طويل جدًا.

ومع ذلك، يمكنك فقط استخدام impl Trait إذا كنت تعيد نوعًا واحدًا فقط. على سبيل المثال، هذا الكود الذي يعيد إما NewsArticle أو SocialPost مع تحديد نوع الإرجاع كـ 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 SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    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 {
        SocialPost {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            repost: false,
        }
    }
}

إرجاع إما NewsArticle أو SocialPost غير مسموح به بسبب قيود حول كيفية تنفيذ الـ impl Trait syntax في المترجم. سنغطي كيفية كتابة دالة بهذا السلوك في قسم “استخدام كائنات السمات للتجريد فوق السلوك المشترك” من الفصل 18.

استخدام قيود السمات لتنفيذ الدوال بشكل مشروط (Using Trait Bounds to Conditionally Implement Methods)

باستخدام trait bound مع كتلة impl تستخدم معاملات أنواع عامة، يمكننا تنفيذ الـ methods بشكل مشروط للأنواع التي تنفذ الـ traits المحددة. على سبيل المثال، النوع Pair<T> في القائمة 10-15 ينفذ دائمًا دالة new لإعادة instance جديد من Pair<T> (تذكر من قسم “بناء جملة الدالة” في الفصل 5 أن Self هو اسم مستعار لنوع كتلة impl وهو في هذه الحالة Pair<T>). ولكن في كتلة impl التالية، ينفذ Pair<T> دالة cmp_display فقط إذا كان نوعه الداخلي T ينفذ الـ PartialOrd trait الذي يتيح المقارنة و الـ Display trait الذي يتيح الطباعة.

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);
        }
    }
}

يمكننا أيضًا تنفيذ trait بشكل مشروط لأي نوع ينفذ trait آخر. تسمى تنفيذات trait على أي نوع يستوفي الـ trait bounds بـ التنفيذات الشاملة (blanket implementations) وتستخدم على نطاق واسع في مكتبة Rust القياسية. على سبيل المثال، تنفذ المكتبة القياسية الـ ToString trait على أي نوع ينفذ الـ Display trait. تبدو كتلة impl في المكتبة القياسية مشابهة لهذا الكود:

impl<T: Display> ToString for T {
    // --snip--
}

نظرًا لأن المكتبة القياسية تمتلك هذا الـ blanket implementation، يمكننا استدعاء دالة to_string المعرفة بواسطة الـ ToString trait على أي نوع ينفذ الـ Display trait. على سبيل المثال، يمكننا تحويل الأعداد الصحيحة (integers) إلى قيم String المقابلة لها بهذا الشكل لأن الأعداد الصحيحة تنفذ Display:

#![allow(unused)]
fn main() {
let s = 3.to_string();
}

تظهر الـ blanket implementations في التوثيق الخاص بالـ trait في قسم “المنفذون” (Implementors).

تتيح لنا الـ traits والـ trait bounds كتابة كود يستخدم معاملات أنواع عامة لتقليل التكرار ولكنها تحدد أيضًا للمترجم أننا نريد أن يمتلك النوع العام سلوكًا معينًا. يمكن للمترجم بعد ذلك استخدام معلومات الـ trait bound للتحقق من أن جميع الأنواع الملموسة المستخدمة مع كودنا توفر السلوك الصحيح. في اللغات ذات الأنواع الديناميكية (dynamically typed languages)، سنحصل على خطأ في وقت التشغيل (runtime) إذا استدعينا دالة على نوع لم يحدد تلك الدالة. لكن Rust ينقل هذه الأخطاء إلى وقت الترجمة (compile time) بحيث نضطر إلى إصلاح المشكلات قبل أن يتمكن كودنا من العمل. بالإضافة إلى ذلك، لا يتعين علينا كتابة كود يتحقق من السلوك في وقت التشغيل، لأننا تحققنا بالفعل في وقت الترجمة. القيام بذلك يحسن الأداء دون الحاجة إلى التخلي عن مرونة الأنواع العامة (generics).