جلب المسارات إلى النطاق باستخدام الكلمة المفتاحية use (Bringing Paths into Scope with the use Keyword)
قد يكون الاضطرار إلى كتابة (المسارات) paths بالكامل لاستدعاء الدوال أمراً غير مريح ومتكرراً. في القائمة 7-7، سواء اخترنا المسار المطلق أو النسبي لدالة add_to_waitlist ، كان علينا في كل مرة نريد فيها استدعاء add_to_waitlist تحديد front_of_house و hosting أيضاً. لحسن الحظ، هناك طريقة لتبسيط هذه العملية: يمكننا إنشاء اختصار لمسار باستخدام الكلمة المفتاحية use مرة واحدة، ثم استخدام الاسم الأقصر في أي مكان آخر في (النطاق) scope.
في القائمة 7-11، نقوم بجلب (الوحدة) module المسماة crate::front_of_house::hosting إلى نطاق دالة eat_at_restaurant بحيث نضطر فقط إلى تحديد hosting::add_to_waitlist لاستدعاء دالة add_to_waitlist في eat_at_restaurant.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
إضافة use ومسار في نطاق ما يشبه إنشاء (رابط رمزي) symbolic link في نظام الملفات. من خلال إضافة use crate::front_of_house::hosting في جذر الصندوق، أصبح hosting الآن اسماً صالحاً في ذلك النطاق، تماماً كما لو تم تعريف وحدة hosting في جذر الصندوق. المسارات التي يتم جلبها إلى النطاق باستخدام use تخضع أيضاً لفحص (الخصوصية) privacy ، مثل أي مسارات أخرى.
لاحظ أن use تنشئ الاختصار فقط للنطاق المحدد الذي تظهر فيه use. تقوم القائمة 7-12 بنقل دالة eat_at_restaurant إلى وحدة فرعية جديدة تسمى customer ، والتي تعد نطاقاً مختلفاً عن عبارة use ، لذا لن يتم تجميع جسم الدالة.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting;
mod customer {
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
}
يظهر خطأ المترجم أن الاختصار لم يعد ينطبق داخل وحدة customer:
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
--> src/lib.rs:11:9
|
11 | hosting::add_to_waitlist();
| ^^^^^^^ use of unresolved module or unlinked crate `hosting`
|
= help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
|
10 + use crate::hosting;
|
warning: unused import: `crate::front_of_house::hosting`
--> src/lib.rs:7:5
|
7 | use crate::front_of_house::hosting;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted
لاحظ أن هناك أيضاً تحذيراً بأن use لم تعد مستخدمة في نطاقها! لإصلاح هذه المشكلة، انقل use داخل وحدة customer أيضاً، أو أشر إلى الاختصار في الوحدة الأب باستخدام super::hosting داخل الوحدة الفرعية customer.
إنشاء مسارات use اصطلاحية (Creating Idiomatic use Paths)
في القائمة 7-11، ربما تساءلت لماذا حددنا use crate::front_of_house::hosting ثم استدعينا hosting::add_to_waitlist في eat_at_restaurant ، بدلاً من تحديد مسار use بالكامل وصولاً إلى دالة add_to_waitlist لتحقيق نفس النتيجة، كما في القائمة 7-13.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting::add_to_waitlist;
pub fn eat_at_restaurant() {
add_to_waitlist();
}
على الرغم من أن كلاً من القائمة 7-11 والقائمة 7-13 تنجزان نفس المهمة، إلا أن القائمة 7-11 هي الطريقة (الاصطلاحية) idiomatic لجلب دالة إلى النطاق باستخدام use. جلب الوحدة الأب للدالة إلى النطاق باستخدام use يعني أنه يجب علينا تحديد الوحدة الأب عند استدعاء الدالة. تحديد الوحدة الأب عند استدعاء الدالة يجعل من الواضح أن الدالة ليست معرفة محلياً مع تقليل تكرار المسار الكامل. الكود في القائمة 7-13 غير واضح فيما يتعلق بمكان تعريف add_to_waitlist.
من ناحية أخرى، عند جلب (الهياكل) structs و (التعدادات) enums والعناصر الأخرى باستخدام use ، فمن الاصطلاحي تحديد المسار الكامل. تعرض القائمة 7-14 الطريقة الاصطلاحية لجلب هيكل HashMap من المكتبة القياسية إلى نطاق صندوق ثنائي.
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
لا يوجد سبب قوي وراء هذا الاصطلاح: إنه مجرد العرف الذي ظهر، واعتاد الناس على قراءة وكتابة كود Rust بهذه الطريقة.
الاستثناء من هذا الاصطلاح هو إذا كنا نجلب عنصرين بنفس الاسم إلى النطاق باستخدام عبارات use ، لأن Rust لا تسمح بذلك. تعرض القائمة 7-15 كيفية جلب نوعين من Result إلى النطاق لهما نفس الاسم ولكن وحدات أب مختلفة، وكيفية الإشارة إليهما.
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
كما ترى، فإن استخدام الوحدات الأب يميز بين نوعي Result. إذا حددنا بدلاً من ذلك use std::fmt::Result و use std::io::Result ، فسيكون لدينا نوعان من Result في نفس النطاق، ولن تعرف Rust أيهما نقصد عندما نستخدم Result.
توفير أسماء جديدة باستخدام الكلمة المفتاحية as (Providing New Names with the as Keyword)
هناك حل آخر لمشكلة جلب نوعين من نفس الاسم إلى نفس النطاق باستخدام use: بعد المسار، يمكننا تحديد as واسم محلي جديد، أو (اسم مستعار) alias ، للنوع. تعرض القائمة 7-16 طريقة أخرى لكتابة الكود في القائمة 7-15 عن طريق إعادة تسمية أحد نوعي Result باستخدام as.
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
في عبارة use الثانية، اخترنا الاسم الجديد IoResult لنوع std::io::Result ، والذي لن يتعارض مع Result من std::fmt الذي جلبناه أيضاً إلى النطاق. تعتبر كل من القائمة 7-15 والقائمة 7-16 اصطلاحية، لذا فالخيار متروك لك!
إعادة تصدير الأسماء باستخدام pub use (Re-exporting Names with pub use)
عندما نجلب اسماً إلى النطاق باستخدام الكلمة المفتاحية use ، يكون الاسم خاصاً بالنطاق الذي استوردناه إليه. لتمكين الكود خارج ذلك النطاق من الإشارة إلى ذلك الاسم كما لو كان قد تم تعريفه في ذلك النطاق، يمكننا الجمع بين pub و use. تسمى هذه التقنية (إعادة التصدير) re-exporting لأننا نجلب عنصراً إلى النطاق ولكننا نجعله متاحاً أيضاً للآخرين لجلبه إلى نطاقهم.
تعرض القائمة 7-17 الكود في القائمة 7-11 مع تغيير use في الوحدة الجذرية إلى pub use.
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
}
قبل هذا التغيير، كان على الكود الخارجي استدعاء دالة add_to_waitlist باستخدام المسار restaurant::front_of_house::hosting::add_to_waitlist() ، والذي كان سيتطلب أيضاً وضع علامة pub على وحدة front_of_house. الآن بعد أن قامت pub use بإعادة تصدير وحدة hosting من الوحدة الجذرية، يمكن للكود الخارجي استخدام المسار restaurant::hosting::add_to_waitlist() بدلاً من ذلك.
تعد إعادة التصدير مفيدة عندما يكون الهيكل الداخلي لكودك مختلفاً عن الطريقة التي يفكر بها المبرمجون الذين يستدعون كودك حول المجال. على سبيل المثال، في استعارة المطعم هذه، يفكر الأشخاص الذين يديرون المطعم في “واجهة المطعم” و “خلفية المطعم”. لكن الزبائن الذين يزورون المطعم ربما لن يفكروا في أجزاء المطعم بهذه المصطلحات. باستخدام pub use ، يمكننا كتابة كودنا بهيكل واحد ولكن كشف هيكل مختلف. القيام بذلك يجعل مكتبتنا منظمة بشكل جيد للمبرمجين الذين يعملون على المكتبة والمبرمجين الذين يستدعون المكتبة. سنلقي نظرة على مثال آخر لـ pub use وكيف تؤثر على توثيق صندوقك في “تصدير واجهة برمجة تطبيقات عامة مريحة” في الفصل 14.
استخدام الحزم الخارجية (Using External Packages)
في الفصل الثاني، قمنا ببرمجة مشروع لعبة تخمين استخدم حزمة خارجية تسمى rand للحصول على أرقام عشوائية. لاستخدام rand في مشروعنا، أضفنا هذا السطر إلى Cargo.toml:
rand = "0.8.5"
إضافة rand كـ dependency في Cargo.toml يخبر Cargo بتنزيل حزمة rand وأي تبعيات من crates.io وجعل rand متاحاً لمشروعنا.
بعد ذلك، لجلب تعريفات rand إلى نطاق حزمتنا، أضفنا سطر use يبدأ باسم الصندوق، rand ، ودرجنا العناصر التي أردنا جلبها إلى النطاق. تذكر أنه في “توليد رقم عشوائي” في الفصل الثاني، جلبنا (سمة) trait المسماة Rng إلى النطاق واستدعينا دالة rand::thread_rng:
use std::io;
use rand::Rng;
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
}
قام أعضاء مجتمع Rust بتوفير العديد من الحزم في crates.io ، ويتضمن سحب أي منها إلى حزمتك نفس هذه الخطوات: إدراجها في ملف Cargo.toml الخاص بحزمتك واستخدام use لجلب العناصر من صناديقها إلى النطاق.
لاحظ أن المكتبة القياسية std هي أيضاً صندوق خارجي لحزمتنا. نظراً لأن المكتبة القياسية يتم شحنها مع لغة Rust، فلا نحتاج إلى تغيير Cargo.toml لتضمين std. لكننا نحتاج إلى الإشارة إليها باستخدام use لجلب العناصر من هناك إلى نطاق حزمتنا. على سبيل المثال، مع HashMap سنستخدم هذا السطر:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
هذا مسار مطلق يبدأ بـ std ، وهو اسم صندوق المكتبة القياسية.
استخدام المسارات المتداخلة لتنظيف قوائم use الكبيرة (Using Nested Paths to Clean Up use Lists)
إذا كنا نستخدم عناصر متعددة معرفة في نفس الصندوق أو نفس الوحدة، فإن إدراج كل عنصر في سطر خاص به يمكن أن يشغل مساحة رأسية كبيرة في ملفاتنا. على سبيل المثال، هاتان العبارتان use اللتان كانتا لدينا في لعبة التخمين في القائمة 2-4 تجلبان عناصر من std إلى النطاق:
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
بدلاً من ذلك، يمكننا استخدام (المسارات المتداخلة) nested paths لجلب نفس العناصر إلى النطاق في سطر واحد. نقوم بذلك عن طريق تحديد الجزء المشترك من المسار، متبوعاً بنقطتين مزدوجتين، ثم أقواس معقوفة حول قائمة أجزاء المسارات التي تختلف، كما هو موضح في القائمة 7-18.
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..=100);
println!("The secret number is: {secret_number}");
println!("Please input your guess.");
let mut guess = String::new();
io::stdin()
.read_line(&mut guess)
.expect("Failed to read line");
let guess: u32 = guess.trim().parse().expect("Please type a number!");
println!("You guessed: {guess}");
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Equal => println!("You win!"),
}
}
في البرامج الأكبر، يمكن أن يؤدي جلب العديد من العناصر إلى النطاق من نفس الصندوق أو الوحدة باستخدام المسارات المتداخلة إلى تقليل عدد عبارات use المنفصلة المطلوبة بشكل كبير!
يمكننا استخدام مسار متداخل في أي مستوى في المسار، وهو أمر مفيد عند دمج عبارني use تشتركان في مسار فرعي. على سبيل المثال، تعرض القائمة 7-19 عبارني use: واحدة تجلب std::io إلى النطاق وأخرى تجلب std::io::Write إلى النطاق.
use std::io;
use std::io::Write;
الجزء المشترك من هذين المسارين هو std::io ، وهذا هو المسار الأول الكامل. لدمج هذين المسارين في عبارة use واحدة، يمكننا استخدام self في المسار المتداخل، كما هو موضح في القائمة 7-20.
use std::io::{self, Write};
يجلب هذا السطر std::io و std::io::Write إلى النطاق.
استيراد العناصر باستخدام عامل النجمة (Importing Items with the Glob Operator)
إذا أردنا جلب جميع العناصر العامة المعرفة في مسار ما إلى النطاق، فيمكننا تحديد ذلك المسار متبوعاً بـ (عامل النجمة) glob operator وهو *:
#![allow(unused)]
fn main() {
use std::collections::*;
}
تجلب عبارة use هذه جميع العناصر العامة المعرفة في std::collections إلى النطاق الحالي. كن حذراً عند استخدام عامل النجمة! يمكن أن يجعل glob من الصعب معرفة الأسماء الموجودة في النطاق ومكان تعريف الاسم المستخدم في برنامجك. بالإضافة إلى ذلك، إذا قامت التبعية بتغيير تعريفاتها، فإن ما استوردته يتغير أيضاً، مما قد يؤدي إلى أخطاء في المترجم عند ترقية التبعية إذا أضافت التبعية تعريفاً بنفس اسم تعريف خاص بك في نفس النطاق، على سبيل المثال.
غالباً ما يُستخدم عامل النجمة عند الاختبار لجلب كل شيء تحت الاختبار إلى وحدة tests ؛ سنتحدث عن ذلك في “كيفية كتابة الاختبارات” في الفصل 11. يُستخدم عامل النجمة أيضاً أحياناً كجزء من نمط (التمهيد) prelude : راجع توثيق المكتبة القياسية لمزيد من المعلومات حول هذا النمط.