นำ Path เข้า Scope ด้วย Keyword use
การต้องเขียน path เพื่อเรียกฟังก์ชันรู้สึกไม่สะดวกและซ้ำซ้อนได้ ใน
Listing 7-7 ไม่ว่าเราเลือก absolute หรือ relative path ไปยังฟังก์ชัน
add_to_waitlist ทุกครั้งที่เราอยากเรียก add_to_waitlist เราต้องระบุ
front_of_house และ hosting ด้วย โชคดี มีวิธีที่จะทำให้กระบวนการนี้
ง่ายขึ้น — เราสร้าง shortcut ของ path ด้วย keyword use ครั้งเดียว แล้ว
ใช้ชื่อสั้นกว่าทุกที่อื่นใน scope ได้
ใน Listing 7-11 เรานำ module crate::front_of_house::hosting เข้าใน
scope ของฟังก์ชัน 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การเพิ่ม use และ path ใน scope คล้ายกับการสร้าง symbolic link ใน
filesystem ด้วยการเพิ่ม use crate::front_of_house::hosting ใน crate
root hosting ตอนนี้เป็นชื่อ valid ใน scope นั้น เหมือนกับว่า module
hosting ประกาศใน crate root Path ที่นำเข้า scope ด้วย use ก็เช็ค
privacy ด้วย เหมือน path อื่นใด
หมายเหตุว่า use สร้าง shortcut เฉพาะสำหรับ scope ที่ use เกิดเท่านั้น
Listing 7-12 ย้ายฟังก์ชัน eat_at_restaurant เข้า module ลูกใหม่ชื่อ
customer ซึ่งจึงเป็น scope ต่างจาก statement use body ของฟังก์ชันจะ
compile ไม่ผ่าน
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();
}
}
use ใช้ได้แค่ใน scope ที่มันอยู่Compiler error แสดงว่า shortcut ใช้ไม่ได้แล้วภายใน module 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
สังเกตว่ายังมี warning ว่า use ไม่ถูกใช้แล้วใน scope ของมัน! ในการแก้
ปัญหานี้ ย้าย use เข้า module customer ด้วย หรืออ้างถึง shortcut ใน
module พ่อด้วย super::hosting ภายใน module customer ลูก
สร้าง Path use ที่ Idiomatic
ใน Listing 7-11 คุณอาจสงสัยว่าทำไมเราระบุ use crate::front_of_house::hosting
แล้วเรียก hosting::add_to_waitlist ใน eat_at_restaurant แทนการระบุ
path use ตลอดจนถึงฟังก์ชัน add_to_waitlist เพื่อให้ผลเดียวกัน เหมือนใน
Listing 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();
}
add_to_waitlist เข้า scope ด้วย use ซึ่งไม่ idiomaticแม้ทั้ง Listing 7-11 และ Listing 7-13 ทำงานเดียวกัน Listing 7-11 เป็น
วิธี idiomatic ในการนำฟังก์ชันเข้า scope ด้วย use การนำ module พ่อของ
ฟังก์ชันเข้า scope ด้วย use หมายความว่าเราต้องระบุ module พ่อเมื่อเรียก
ฟังก์ชัน การระบุ module พ่อเมื่อเรียกฟังก์ชันทำให้ชัดเจนว่าฟังก์ชันไม่
ประกาศ local ขณะลดการเขียน path เต็มซ้ำ โค้ดใน Listing 7-13 ไม่ชัดเจนว่า
add_to_waitlist ประกาศที่ไหน
ตรงข้าม เมื่อนำ struct, enum และ item อื่นด้วย use มัน idiomatic ที่จะ
ระบุ path เต็ม Listing 7-14 แสดงวิธี idiomatic ในการนำ struct HashMap
ของ standard library เข้า scope ของ binary crate
use std::collections::HashMap;
fn main() {
let mut map = HashMap::new();
map.insert(1, 2);
}
HashMap เข้า scope ในแบบ idiomaticไม่มีเหตุผลแข็งแรงเบื้องหลัง idiom นี้ — มันแค่ convention ที่เกิดขึ้น และคนคุ้นเคยกับการอ่านและเขียนโค้ด Rust ในแบบนี้
ข้อยกเว้นของ idiom นี้คือถ้าเรานำสอง item ที่มีชื่อเดียวกันเข้า scope
ด้วย statement use เพราะ Rust ไม่อนุญาตอย่างนั้น Listing 7-15 แสดงวิธี
นำสอง type Result ที่มีชื่อเดียวกันแต่ module พ่อต่างกันเข้า scope และ
วิธีอ้างถึงพวกมัน
use std::fmt;
use std::io;
fn function1() -> fmt::Result {
// --snip--
Ok(())
}
fn function2() -> io::Result<()> {
// --snip--
Ok(())
}
อย่างที่คุณเห็น การใช้ module พ่อแยก type Result สองตัว ถ้าแทนเราระบุ
use std::fmt::Result และ use std::io::Result เราจะมี type Result
สองตัวใน scope เดียว และ Rust จะไม่รู้ว่าเราหมายถึงตัวไหนเมื่อเราใช้
Result
ให้ชื่อใหม่ด้วย Keyword as
มีอีกคำตอบสำหรับปัญหาของการนำสอง type ที่ชื่อเดียวกันเข้า scope เดียวกัน
ด้วย use — หลัง path เราระบุ as และชื่อ local ใหม่ หรือ alias ของ
type ได้ Listing 7-16 แสดงอีกวิธีในการเขียนโค้ดใน Listing 7-15 โดย
เปลี่ยนชื่อหนึ่งใน type Result สองตัวด้วย as
use std::fmt::Result;
use std::io::Result as IoResult;
fn function1() -> Result {
// --snip--
Ok(())
}
fn function2() -> IoResult<()> {
// --snip--
Ok(())
}
asใน statement use ที่สอง เราเลือกชื่อใหม่ IoResult สำหรับ type
std::io::Result ซึ่งจะไม่ขัดกับ Result จาก std::fmt ที่เราก็นำเข้า
scope ด้วย Listing 7-15 และ Listing 7-16 ถือเป็น idiomatic ดังนั้นการ
เลือกอยู่ที่คุณ!
Re-export ชื่อด้วย pub use
เมื่อเรานำชื่อเข้า scope ด้วย keyword use ชื่อเป็น private ต่อ scope
ที่เรา import เข้า ในการเปิดทางให้โค้ดนอก scope นั้นอ้างถึงชื่อนั้น
ราวกับว่ามันประกาศใน scope นั้น เรารวม pub และ use ได้ เทคนิคนี้
เรียกว่า re-export เพราะเรากำลังนำ item เข้า scope แต่ก็ทำให้ item นั้น
มีให้คนอื่นนำเข้า scope ของพวกเขาด้วย
Listing 7-17 แสดงโค้ดใน Listing 7-11 ที่เปลี่ยน use ใน root module
เป็น 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();
}
pub useก่อนการเปลี่ยนนี้ โค้ดภายนอกจะต้องเรียกฟังก์ชัน add_to_waitlist โดยใช้
path restaurant::front_of_house::hosting::add_to_waitlist() ซึ่งจะ
ต้องการให้ module front_of_house ถูก mark เป็น pub ด้วย ตอนนี้ pub use นี้ได้ re-export module hosting จาก root module โค้ดภายนอกใช้
path restaurant::hosting::add_to_waitlist() แทนได้
Re-export มีประโยชน์เมื่อโครงสร้างภายในของโค้ดของคุณต่างจากที่โปรแกรมเมอร์
ที่เรียกโค้ดของคุณจะคิดเรื่อง domain เช่น ในการเปรียบเทียบร้านอาหารนี้
คนที่ดำเนินร้านอาหารคิดเรื่อง “front of house” และ “back of house” แต่
ลูกค้าที่เยี่ยมร้านอาหารคงไม่คิดเรื่องส่วนของร้านอาหารในคำเหล่านั้น ด้วย
pub use เราเขียนโค้ดของเราด้วยโครงสร้างหนึ่ง แต่เปิดเผยโครงสร้างต่าง
ได้ การทำอย่างนั้นทำให้ library ของเราจัดระเบียบดีสำหรับโปรแกรมเมอร์ที่
ทำงานบน library และโปรแกรมเมอร์ที่เรียก library เราจะดูตัวอย่างอีกของ
pub use และวิธีที่มันส่งผลต่อ documentation ของ crate ของคุณใน
“Export Public API ที่สะดวก” ในบทที่ 14
ใช้ Package ภายนอก
ในบทที่ 2 เราเขียนโปรเจกต์เกมทายตัวเลขที่ใช้ external package ชื่อ rand
เพื่อรับตัวเลขสุ่ม ในการใช้ rand ในโปรเจกต์ของเรา เราเพิ่มบรรทัดนี้ใน
Cargo.toml:
rand = "0.8.5"
การเพิ่ม rand เป็น dependency ใน Cargo.toml บอก Cargo ให้ download
package rand และ dependency ใด ๆ จาก crates.io
และทำให้ rand มีให้โปรเจกต์ของเรา
จากนั้น ในการนำ definition ของ rand เข้า scope ของ package ของเรา เรา
เพิ่มบรรทัด use เริ่มด้วยชื่อ crate rand และ list item ที่เราอยากนำ
เข้า scope จำได้ว่าใน “Generate ตัวเลขสุ่ม” ในบทที่
2 เรานำ trait Rng เข้า scope และเรียกฟังก์ชัน 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 community ทำ package หลายตัวให้ที่
crates.io และการดึงพวกมันใด ๆ เข้า package ของคุณ
เกี่ยวข้องกับขั้นตอนเดียวกัน — list พวกมันในไฟล์ Cargo.toml ของ package
ของคุณ และใช้ use นำ item จาก crate ของพวกมันเข้า scope
หมายเหตุว่า library std มาตรฐานก็เป็น crate ที่ external ต่อ package
ของเรา เพราะ standard library มากับภาษา Rust เราไม่ต้องเปลี่ยน
Cargo.toml เพื่อรวม std แต่เราต้องอ้างถึงมันด้วย use เพื่อนำ item
จากตรงนั้นเข้า scope ของ package เรา เช่น กับ HashMap เราจะใช้บรรทัด
นี้:
#![allow(unused)]
fn main() {
use std::collections::HashMap;
}
นี่เป็น absolute path ที่เริ่มด้วย std ชื่อของ standard library crate
ใช้ Nested Path เพื่อทำความสะอาด List use
ถ้าเราใช้ item หลายตัวที่ประกาศใน crate หรือ module เดียวกัน การ list
แต่ละ item บนบรรทัดของตัวเองอาจกินที่แนวตั้งเยอะในไฟล์ของเรา เช่น
statement use สองตัวที่เรามีในเกมทายตัวเลขใน Listing 2-4 นำ item จาก
std เข้า scope:
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 path นำ item เดียวกันเข้า scope ในบรรทัดเดียวได้ เราทำ สิ่งนี้โดยระบุส่วนที่เหมือนกันของ path ตามด้วย colon สองตัว แล้ว curly bracket รอบ list ของส่วนของ path ที่ต่างกัน ดังที่แสดงใน Listing 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!"),
}
}
ในโปรแกรมใหญ่ขึ้น การนำ item หลายตัวเข้า scope จาก crate หรือ module
เดียวกันโดยใช้ nested path ลดจำนวน statement use แยกที่ต้องการได้เยอะ!
เราใช้ nested path ในระดับใดของ path ได้ ซึ่งมีประโยชน์เมื่อรวม statement
use สองตัวที่แชร์ subpath เช่น Listing 7-19 แสดง statement use สอง
ตัว — ตัวหนึ่งที่นำ std::io เข้า scope และตัวหนึ่งที่นำ std::io::Write
เข้า scope
use std::io;
use std::io::Write;
use สองตัวที่ตัวหนึ่งเป็น subpath ของอีกตัวส่วนที่เหมือนกันของสอง path นี้คือ std::io และนั่นคือ path แรกสมบูรณ์
ในการรวมสอง path นี้เป็น statement use เดียว เราใช้ self ใน nested
path ได้ ดังที่แสดงใน Listing 7-20
use std::io::{self, Write};
use เดียวบรรทัดนี้นำ std::io และ std::io::Write เข้า scope
Import Item ด้วย Glob Operator
ถ้าเราอยากนำ public item ทั้งหมด ที่ประกาศใน path เข้า scope เราระบุ
path นั้นตามด้วย glob operator *:
#![allow(unused)]
fn main() {
use std::collections::*;
}
statement use นี้นำ public item ทั้งหมดที่ประกาศใน std::collections
เข้า scope ปัจจุบัน ระวังเมื่อใช้ glob operator! Glob ทำให้ยากขึ้นที่จะ
บอกว่าชื่อไหนอยู่ใน scope และที่ที่ชื่อที่ใช้ในโปรแกรมของคุณถูกประกาศ
นอกจากนี้ ถ้า dependency เปลี่ยน definition สิ่งที่คุณ import เปลี่ยน
ด้วย ซึ่งอาจนำไปสู่ compiler error เมื่อคุณ upgrade dependency ถ้า
dependency เพิ่ม definition ที่มีชื่อเหมือน definition ของคุณใน scope
เดียวกัน เช่น
glob operator มักใช้เมื่อทำการทดสอบ เพื่อนำทุกอย่างภายใต้การทดสอบเข้า
module tests เราจะพูดถึงเรื่องนั้นใน
“วิธีเขียนเทส” ในบทที่ 11 glob operator
บางครั้งก็ใช้เป็นส่วนหนึ่งของ prelude pattern ดู
documentation ของ standard library
สำหรับข้อมูลเพิ่มเติมเรื่อง pattern นั้น