English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية
القياسية هي آلية لا يمكن الاستغناء عنها في لغة البرمجة.
يستخدم لغة C++ "النماذج" لتحقيق القياسية، بينما لا تحتوي لغة C على آلية القياسية، مما يجعل من الصعب بناء مشاريع معقدة من النوع.
هيكل القياسية هو آلية يستخدمها لغة البرمجة لتعبير عن التعبيرات النوعية، وتستخدم عادةً في الأنواع التي تكون الوظيفة معروفة، وأنواع البيانات غير معروفة، مثل قوائم الاتصال، وتتبع البيانات، إلخ.
هذه طريقة ترتيب الأعداد الصحيحة
fn max(array: &[i32]) -> i32 { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] } fn main() { let a = [2, 4, 6, 3, 1]; println!("max = {}", max(&a)); }
نتيجة التنفيذ:
max = 6
هذا برنامج بسيط للحصول على أكبر قيمة، يمكن استخدامه لمعالجة بيانات النوع i32، لكنه لا يمكن استخدامه لبيانات النوع f64. يمكننا استخدام القياسية لجعل هذه الوظيفة قابلة للتطبيق على أنواع مختلفة. ومع ذلك، ليست جميع أنواع البيانات قابلة للقياس، لذا فإن الكود التالي ليس لتنفيذ، بل لوصف بنية القياسية للوظيفة:
fn max<T>(array: &[T]) -> T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i] > array[max_index] { max_index = i; } i += 1; } array[max_index] }
في السابق تعلمنا أن كلاً من Option و Result هو نوعين من التعبيرات القياسية الموجهة.
يمكن للبنيات التحتية والقوائم في Rust تنفيذ ميكانيكية الجنس.]}
struct Point<T> { x: T, y: T }
هذه هي بنية النقطة الائتمانية، T تمثل النوع الرقمي الذي يصف إحداثيات النقطة. يمكننا استخدامها مثل هذا:
let p1 = Point { x: 1, y: 2}; let p2 = Point { x: 1.0, y: 2.0};
لا يتم إعلان النوع عند الاستخدام، يتم استخدام آلية التوليد التلقائي للنوع هنا، ولكن لا يُسمح بظهور تباين في النوع مثل هذا:
let p = Point { x: 1, y: 2.0};
عند ارتباط x مع 1 يتم تحديد T كـ i32، لذا لا يُسمح بظهور نوع f64. إذا أردنا أن يستخدم x و y أنواع بيانات مختلفة، يمكننا استخدام علامات الجنس المزدوجة:
struct Point<T1, T2> { x: T1, y: T2 }
تمثل طرق الجنس في الفئات مثل Option و Result في الفئات:
enum Option<T> { Some(T), None, } enum Result<T, E> { Ok(T), Err(E), }
يمكن للبنية التحتية والقوائم أن تعرف الطرق، لذا يجب أن يطبق ميكانيكية الجنس أيضًا على الطرق، وإلا لن يمكن للفئات الجنسية أن تُعمل بشكل فعال بالطرق.
struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } } fn main() { let p = Point { x: 1, y: 2 }; println!("p.x = {}", p.x()); }
نتيجة التنفيذ:
p.x = 1
لاحظ، يجب أن يكون هناك <T> بعد كلمة impl، لأن T التي تأتي بعدها تستخدمها كنموذج. ولكن يمكننا أيضًا إضافة طريقة واحدة للجنس من بينها:
impl Point<f64> { fn x(&self) -> f64 { self.x } }
لا يحول حاجز الجنس للكتلة نفسها من التسبب في امتلاك الطرق الداخلية للجنس:
impl<T, U> Point<T, U> { fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> { Point { x: self.x, y: other.y, } } }
طريقة mixup تدمج x من نقطة Point<T, U> مع y من نقطة Point<V, W> لإنشاء نقطة جديدة من نوع Point<T, W>.
特性(trait)概念接近于 Java 中的接口(Interface),但两者不完全相同。特性与接口相同的地方在于它们都是一种行为规范,可以用于标识哪些类有哪些方法。
特性在 Rust 中用 trait 表示:
trait Descriptive { fn describe(&self) -> String; }
Descriptive 指定了实现者必须有是 describe(&self) -> String 方法。
我们用它实现一个结构体:
struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } }
格式是:
impl <特性名> for <所实现的类型名>
Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。
这是特性与接口的不同点:接口只能规范方法而不能定义方法,但特性可以定义方法作为默认方法,因为是"默认",所以对象既可以重新定义方法,也可以不重新定义方法使用默认的方法:
trait Descriptive { fn describe(&self) -> String { String::from("[Object]") } } struct Person { name: String, age: u8 } impl Descriptive for Person { fn describe(&self) -> String { format!("{} {}", self.name, self.age) } } fn main() { let cali = Person { الاسم: String::from("Cali"), العمر: 24 }; println!("{}", cali.describe()); }
نتيجة التنفيذ:
Cali 24
如果我们将 impl Descriptive for Person 块中的内容去掉,那么运行结果就是:
[Object]
很多情况下我们需要传递一个函数做参数,例如回调函数、设置按钮事件等。在 Java 中函数必须以接口实现的类示例来传递,在 Rust 中可以通过传递特性参数来实现:
fn output(object: impl Descriptive) { println!("{}", object.describe()); }
任何实现了 Descriptive 特性的对象都可以作为这个函数的参数,这个函数没必要了解传入对象有没有其他属性或方法,只需要了解它一定有 Descriptive 特性规范的方法就可以了。当然,此函数内也无法使用其他的属性与方法。
特性参数还可以用这种等效语法实现:
fn output<T: Descriptive>(object: T) { println!("{}", object.describe()); }
هذا هو لغة التجميل التي تشبه الجماعات، وتكون مفيدة جدًا عندما تكون جميع أنواع المعلمات خصائص، مثل:
fn output_two<T: Descriptive>(arg1: T, arg2: T) { println!("{}", arg1.describe()); println!("{}", arg2.describe()); }
عند استخدام الخصائص كنوع، يمكن استخدام رمز + لتمثيل أكثر من خاصية، مثل:
fn notify(item: impl Summary + Display) fn notify<T: Summary + Display>(item: T)
ملاحظة:عند استخدامها كنوع، لا يعني ذلك أنه يمكن استخدامها في الكتلة impl.
يمكن استخدام كلمة المفتاح where لتسهيل العلاقات التنسيقية المعقدة، مثل:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U)
يمكن تبسيطها إلى:
fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug
في فهم هذه الجملة، يمكن تنفيذ حالة "الحصول على أكبر قيمة" في فصل الجماعات بشكل حقيقي:
trait Comparable { fn compare(&self, object: &Self) -> i8; } fn max<T: Comparable>(array: &[T]) -> &T { let mut max_index = 0; let mut i = 1; while i < array.len() { if array[i].compare(&array[max_index]) > 0 { max_index = i; } i += 1; } &array[max_index] } impl Comparable for f64 { fn compare(&self, object: &f64) -> i8 { if &self > &object { 1 } else if &self == &object { 0 } else { -1 } } } fn main() { let arr = [1.0, 3.0, 5.0, 4.0, 2.0]; println!("أكبر عدد في arr هو {}", max(&arr)); }
نتيجة التنفيذ:
أكبر عدد في arr هو 5
نصيحة: بما أن إعلان وظيفة compare الثانية يجب أن يكون من نفس النوع الذي تم تنفيذه للصفة، فإن كلمة Self (ملاحظة الحروف الكبيرة) تعني النوع الحالي (ليس المثال) نفسه.
تنسيق الصفات كقيمة العودة هو:
fn person() -> impl Descriptive { Person { الاسم: String::from("Cali"), العمر: 24 } }
لكن هناك نقطه، يمكن أن تأخذ الصفات كقيمة العودة فقط الأجسام التي تم تنفيذ الصفة الخاصة بها، و يجب أن تكون جميع أنواع العودة المحتملة في نفس الدالة متطابقة تماماً. على سبيل المثال، إذا كانت بنية A و B قد تم تنفيذ الصفة Trait، فإن الدالة التالية هي خطأ:
fn some_function(bool bl) -> impl Descriptive { if bl { else { return A {}; } } return B {}; } }
الوظيفة المدمجة قوية للغاية، يمكننا استخدامها لإنشاء طرق الفئة. ولكن بالنسبة للفئات المدمجة، في بعض الأحيان نحتاج إلى التمييز بين الطرق التي تم تنفيذها من قبل الفئة المدمجة لتحديد الطريقة التي يجب تنفيذها في المستقبل:
struct A<T> {} impl<T: B + C> A<T> { fn d(&self) {} }
يُفترض أن يتم تنفيذ نوع A<T> فقط إذا تم تنفيذ الصفات B وC الخاصة بـ T.