Path สำหรับอ้างถึง Item ใน Module Tree
ในการแสดงให้ Rust รู้ว่าจะหา item ใน module tree ที่ไหน เราใช้ path ใน แบบเดียวกับที่เราใช้ path เมื่อ navigate filesystem ในการเรียกฟังก์ชัน เราต้องรู้ path ของมัน
Path เป็นได้สองรูปแบบ:
- absolute path คือ path เต็มที่เริ่มจาก crate root สำหรับโค้ดจาก
external crate absolute path เริ่มด้วยชื่อ crate และสำหรับโค้ดจาก crate
ปัจจุบัน มันเริ่มด้วย literal
crate - relative path เริ่มจาก module ปัจจุบัน และใช้
self,superหรือ identifier ใน module ปัจจุบัน
ทั้ง absolute และ relative path ตามด้วย identifier หนึ่งหรือมากกว่าที่
คั่นด้วย double colon (::)
กลับไปที่ Listing 7-1 สมมติเราอยากเรียกฟังก์ชัน add_to_waitlist นี่
เหมือนกับถามว่า — path ของฟังก์ชัน add_to_waitlist คืออะไร? Listing 7-3
มี Listing 7-1 ที่ลบ module และฟังก์ชันบางตัวออก
เราจะแสดงสองวิธีในการเรียกฟังก์ชัน add_to_waitlist จากฟังก์ชันใหม่
eat_at_restaurant ที่ประกาศใน crate root path เหล่านี้ถูก แต่มีปัญหา
อีกอันที่จะป้องกันไม่ให้ตัวอย่างนี้ compile ได้ตามที่เป็น เราจะอธิบายว่า
ทำไมในไม่ช้า
ฟังก์ชัน eat_at_restaurant เป็นส่วนหนึ่งของ public API ของ library
crate เรา เราจึง mark มันด้วย keyword pub ในส่วน
“เปิดเผย Path ด้วย Keyword pub” เราจะลงรายละเอียด
มากขึ้นเรื่อง pub
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
}
}
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
add_to_waitlist ด้วย absolute และ relative pathครั้งแรกที่เราเรียกฟังก์ชัน add_to_waitlist ใน eat_at_restaurant เรา
ใช้ absolute path ฟังก์ชัน add_to_waitlist ประกาศใน crate เดียวกับ
eat_at_restaurant ซึ่งหมายความว่าเราใช้ keyword crate เริ่ม absolute
path ได้ จากนั้นเรารวมแต่ละ module ที่ต่อกันจนกระทั่งเราเดินไปถึง
add_to_waitlist คุณนึกถึง filesystem ที่มีโครงสร้างเดียวกันได้ — เรา
ระบุ path /front_of_house/hosting/add_to_waitlist เพื่อรันโปรแกรม
add_to_waitlist การใช้ชื่อ crate เริ่มจาก crate root เหมือนการใช้ /
เริ่มจาก filesystem root ใน shell ของคุณ
ครั้งที่สองที่เราเรียก add_to_waitlist ใน eat_at_restaurant เราใช้
relative path path เริ่มด้วย front_of_house ชื่อของ module ที่ประกาศ
ในระดับเดียวกันของ module tree กับ eat_at_restaurant ที่นี่สิ่งเทียบ
เท่า filesystem จะเป็นการใช้ path front_of_house/hosting/add_to_waitlist
การเริ่มด้วยชื่อ module หมายความว่า path เป็น relative
การเลือกใช้ relative หรือ absolute path เป็นการตัดสินใจที่คุณจะทำตาม
โปรเจกต์ของคุณ และขึ้นกับว่าคุณมีโอกาสมากกว่าที่จะย้ายโค้ดการประกาศ item
แยกจากหรือพร้อมกับโค้ดที่ใช้ item เช่น ถ้าเราย้าย module front_of_house
และฟังก์ชัน eat_at_restaurant เข้า module ชื่อ customer_experience
เราต้อง update absolute path ไปยัง add_to_waitlist แต่ relative path
จะยัง valid อย่างไรก็ตาม ถ้าเราย้ายฟังก์ชัน eat_at_restaurant แยกเข้า
module ชื่อ dining absolute path ไปยังการเรียก add_to_waitlist จะ
ยังเหมือนเดิม แต่ relative path จะต้อง update ความชอบของเราโดยทั่วไปคือ
ระบุ absolute path เพราะมีโอกาสมากกว่าที่เราจะอยากย้ายการประกาศโค้ดและ
การเรียก item อย่างอิสระจากกัน
ลอง compile Listing 7-3 และดูว่าทำไมมันยัง compile ไม่ได้! Error ที่เรา ได้แสดงใน Listing 7-4
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: module `hosting` is private
--> src/lib.rs:9:28
|
9 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
error[E0603]: module `hosting` is private
--> src/lib.rs:12:21
|
12 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^ --------------- function `add_to_waitlist` is not publicly re-exported
| |
| private module
|
note: the module `hosting` is defined here
--> src/lib.rs:2:5
|
2 | mod hosting {
| ^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
Error message บอกว่า module hosting เป็น private พูดอีกอย่าง เรามี
path ที่ถูกต้องสำหรับ module hosting และฟังก์ชัน add_to_waitlist แต่
Rust ไม่ให้เราใช้พวกมัน เพราะมันไม่มีการเข้าถึงส่วน private ใน Rust item
ทั้งหมด (ฟังก์ชัน, เมธอด, struct, enum, module และ constant) เป็น
private จาก module พ่อโดย default ถ้าคุณอยากทำให้ item อย่างฟังก์ชันหรือ
struct เป็น private คุณใส่มันใน module
Item ใน module พ่อใช้ private item ภายใน module ลูกไม่ได้ แต่ item ใน module ลูกใช้ item ใน module บรรพบุรุษได้ นี่เพราะ module ลูกห่อและซ่อน รายละเอียด implementation ของพวกมัน แต่ module ลูกเห็นบริบทที่พวกมันถูก ประกาศได้ ต่อกับการเปรียบเทียบของเรา คิดถึงกฎ privacy เหมือน back office ของร้านอาหาร — สิ่งที่เกิดในนั้นเป็น private ต่อลูกค้าร้านอาหาร แต่ผู้ จัดการ office เห็นและทำทุกอย่างในร้านอาหารที่พวกเขาดำเนินการ
Rust เลือกให้ระบบ module ทำงานในแบบนี้ เพื่อให้การซ่อนรายละเอียด
implementation ภายในเป็น default แบบนั้น คุณรู้ว่าส่วนไหนของโค้ดภายในที่
คุณเปลี่ยนได้โดยไม่ทำให้โค้ดภายนอกเสีย อย่างไรก็ตาม Rust ให้ตัวเลือกคุณ
ในการเปิดเผยส่วนภายในของโค้ด module ลูกให้ module บรรพบุรุษภายนอก โดยใช้
keyword pub เพื่อทำให้ item เป็น public
เปิดเผย Path ด้วย Keyword pub
มากลับไปที่ error ใน Listing 7-4 ที่บอกเราว่า module hosting เป็น
private เราอยากให้ฟังก์ชัน eat_at_restaurant ใน module พ่อมีการเข้าถึง
ฟังก์ชัน add_to_waitlist ใน module ลูก เราจึง mark module hosting
ด้วย keyword pub ดังที่แสดงใน Listing 7-5
mod front_of_house {
pub mod hosting {
fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
hosting เป็น pub เพื่อใช้จาก eat_at_restaurantน่าเสียดาย โค้ดใน Listing 7-5 ยังคงให้ compiler error ดังที่แสดงใน Listing 7-6
$ cargo build
Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:10:37
|
10 | crate::front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
error[E0603]: function `add_to_waitlist` is private
--> src/lib.rs:13:30
|
13 | front_of_house::hosting::add_to_waitlist();
| ^^^^^^^^^^^^^^^ private function
|
note: the function `add_to_waitlist` is defined here
--> src/lib.rs:3:9
|
3 | fn add_to_waitlist() {}
| ^^^^^^^^^^^^^^^^^^^^
For more information about this error, try `rustc --explain E0603`.
error: could not compile `restaurant` (lib) due to 2 previous errors
เกิดอะไรขึ้น? การเพิ่ม keyword pub หน้า mod hosting ทำให้ module เป็น
public ด้วยการเปลี่ยนนี้ ถ้าเราเข้าถึง front_of_house ได้ เราเข้าถึง
hosting ได้ แต่ เนื้อหา ของ hosting ยังเป็น private — การทำให้
module เป็น public ไม่ทำให้เนื้อหาของมันเป็น public Keyword pub บน
module เพียงให้โค้ดใน module บรรพบุรุษอ้างถึงมัน ไม่ให้เข้าถึงโค้ดภายใน
เพราะ module เป็น container ไม่มีอะไรมากที่เราทำได้โดยทำให้แค่ module
เป็น public — เราต้องไปไกลกว่าและเลือกทำให้ item หนึ่งหรือมากกว่าภายใน
module เป็น public ด้วย
Error ใน Listing 7-6 บอกว่าฟังก์ชัน add_to_waitlist เป็น private กฎ
privacy ใช้กับ struct, enum, ฟังก์ชันและเมธอด รวมถึง module
มาทำให้ฟังก์ชัน add_to_waitlist เป็น public ด้วย โดยเพิ่ม keyword
pub ก่อนการประกาศของมัน เหมือนใน Listing 7-7
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
// -- snip --
pub fn eat_at_restaurant() {
// Absolute path
crate::front_of_house::hosting::add_to_waitlist();
// Relative path
front_of_house::hosting::add_to_waitlist();
}
pub ให้ mod hosting และ fn add_to_waitlist ให้เราเรียกฟังก์ชันจาก eat_at_restaurantตอนนี้โค้ดจะ compile ผ่าน! เพื่อดูว่าทำไมการเพิ่ม keyword pub ให้เราใช้
path เหล่านี้ใน eat_at_restaurant ตามกฎ privacy ได้ มาดู absolute และ
relative path
ใน absolute path เราเริ่มด้วย crate ที่ root ของ module tree ของ crate
เรา module front_of_house ประกาศใน crate root ขณะที่ front_of_house
ไม่ public แต่เพราะฟังก์ชัน eat_at_restaurant ประกาศใน module เดียวกับ
front_of_house (นั่นคือ eat_at_restaurant และ front_of_house เป็น
sibling) เราอ้างถึง front_of_house จาก eat_at_restaurant ได้ ถัดไปคือ
module hosting ที่ mark ด้วย pub เราเข้าถึง module พ่อของ hosting
ได้ จึงเข้าถึง hosting ได้ สุดท้าย ฟังก์ชัน add_to_waitlist ถูก mark
ด้วย pub และเราเข้าถึง module พ่อของมันได้ การเรียกฟังก์ชันนี้จึงทำงาน!
ใน relative path logic เหมือน absolute path ยกเว้นขั้นแรก — แทนการเริ่ม
จาก crate root path เริ่มจาก front_of_house Module front_of_house
ประกาศภายใน module เดียวกันกับ eat_at_restaurant relative path ที่เริ่ม
จาก module ที่ eat_at_restaurant ประกาศจึงทำงาน จากนั้น เพราะ hosting
และ add_to_waitlist ถูก mark ด้วย pub ส่วนที่เหลือของ path ทำงาน
และการเรียกฟังก์ชันนี้ valid!
ถ้าคุณวางแผนแชร์ library crate เพื่อให้โปรเจกต์อื่นใช้โค้ดของคุณ public API ของคุณคือสัญญากับ user ของ crate ของคุณ ที่กำหนดวิธีที่พวกเขาโต้ตอบ กับโค้ดของคุณ มีข้อพิจารณาเยอะเรื่องการจัดการการเปลี่ยนใน public API เพื่อทำให้ง่ายขึ้นที่คนจะพึ่ง crate ของคุณ ข้อพิจารณาเหล่านี้อยู่นอก ขอบเขตของหนังสือนี้ ถ้าคุณสนใจหัวข้อนี้ ดู Rust API Guidelines
Best Practice สำหรับ Package ที่มีทั้ง Binary และ Library
เราเอ่ยว่า package มีทั้ง src/main.rs binary crate root และ src/lib.rs library crate root ได้ และทั้งสอง crate จะมีชื่อของ package โดย default โดยปกติ package ที่มี pattern นี้ของการมีทั้ง library และ binary crate จะมีโค้ดใน binary crate เพียงพอที่จะเริ่ม executable ที่เรียกโค้ดที่ประกาศใน library crate สิ่งนี้ให้โปรเจกต์อื่น ได้รับประโยชน์จาก functionality ส่วนใหญ่ที่ package ให้ เพราะโค้ดของ library crate แชร์ได้
Module tree ควรประกาศใน src/lib.rs จากนั้น public item ใด ๆ ใช้ใน binary crate ได้ โดยเริ่ม path ด้วยชื่อ package Binary crate กลายเป็น user ของ library crate เหมือนกับที่ external crate สมบูรณ์จะใช้ library crate — มันใช้ได้แค่ public API เท่านั้น สิ่งนี้ช่วยให้คุณ ออกแบบ API ที่ดี — ไม่ใช่แค่คุณเป็นผู้เขียน คุณยังเป็น client ด้วย!
ใน บทที่ 12 เราจะแสดงการปฏิบัติการจัดระเบียบนี้ กับโปรแกรม command line ที่มีทั้ง binary crate และ library crate
เริ่ม Relative Path ด้วย super
เราสร้าง relative path ที่เริ่มใน module พ่อ แทน module ปัจจุบันหรือ crate
root ได้ โดยใช้ super ที่ต้น path นี่เหมือนการเริ่ม filesystem path
ด้วย syntax .. ที่หมายถึงไป directory พ่อ การใช้ super ให้เราอ้างถึง
item ที่เรารู้ว่าอยู่ใน module พ่อ ซึ่งทำให้การจัดเรียง module tree ใหม่
ง่ายขึ้นเมื่อ module ผูกใกล้กับพ่อ แต่พ่ออาจถูกย้ายไปที่อื่นใน module
tree วันหนึ่ง
พิจารณาโค้ดใน Listing 7-8 ที่ model สถานการณ์ที่ chef แก้ order ที่ผิด
และเอามาให้ลูกค้าด้วยตัวเอง ฟังก์ชัน fix_incorrect_order ที่ประกาศใน
module back_of_house เรียกฟังก์ชัน deliver_order ที่ประกาศใน module
พ่อ โดยระบุ path ไปยัง deliver_order เริ่มด้วย super
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
superฟังก์ชัน fix_incorrect_order อยู่ใน module back_of_house เราจึงใช้
super ไป module พ่อของ back_of_house ได้ ซึ่งในกรณีนี้คือ crate
root จากตรงนั้น เรามองหา deliver_order และเจอมัน สำเร็จ! เราคิดว่า
module back_of_house และฟังก์ชัน deliver_order มีโอกาสจะอยู่ในความ
สัมพันธ์เดียวกันกับกัน และถูกย้ายไปด้วยกัน ถ้าเราตัดสินใจจัดระเบียบ
module tree ของ crate ใหม่ ดังนั้นเราใช้ super เพื่อจะมีที่น้อยกว่าให้
update โค้ดในอนาคต ถ้าโค้ดนี้ถูกย้ายไปที่ module อื่น
ทำให้ Struct และ Enum เป็น Public
เรายังใช้ pub กำหนด struct และ enum เป็น public ได้ แต่มีรายละเอียด
เพิ่มเติมเรื่องการใช้ pub กับ struct และ enum ถ้าเราใช้ pub ก่อนการ
ประกาศ struct เราทำให้ struct เป็น public แต่ field ของ struct จะยัง
เป็น private เราทำให้แต่ละ field เป็น public หรือไม่ได้แบบ case-by-case
ใน Listing 7-9 เราประกาศ struct back_of_house::Breakfast ที่ public
ที่มี field toast public แต่ field seasonal_fruit private สิ่งนี้
model กรณีในร้านอาหารที่ลูกค้าเลือกชนิดขนมปังที่มากับมื้อได้ แต่ chef
ตัดสินใจว่าผลไม้ตัวไหนคู่กับมื้อ ขึ้นกับว่าอะไรอยู่ในฤดูและในสต็อก ผลไม้
ที่มีเปลี่ยนเร็ว ลูกค้าจึงเลือกผลไม้ไม่ได้ และเห็นไม่ได้แม้จะได้ผลไม้
ตัวไหน
mod back_of_house {
pub struct Breakfast {
pub toast: String,
seasonal_fruit: String,
}
impl Breakfast {
pub fn summer(toast: &str) -> Breakfast {
Breakfast {
toast: String::from(toast),
seasonal_fruit: String::from("peaches"),
}
}
}
}
pub fn eat_at_restaurant() {
// Order a breakfast in the summer with Rye toast.
let mut meal = back_of_house::Breakfast::summer("Rye");
// Change our mind about what bread we'd like.
meal.toast = String::from("Wheat");
println!("I'd like {} toast please", meal.toast);
// The next line won't compile if we uncomment it; we're not allowed
// to see or modify the seasonal fruit that comes with the meal.
// meal.seasonal_fruit = String::from("blueberries");
}
เพราะ field toast ใน struct back_of_house::Breakfast เป็น public ใน
eat_at_restaurant เราเขียนและอ่าน field toast โดยใช้ dot notation ได้
สังเกตว่าเราใช้ field seasonal_fruit ใน eat_at_restaurant ไม่ได้
เพราะ seasonal_fruit เป็น private ลอง uncomment บรรทัดที่แก้ค่า field
seasonal_fruit เพื่อดูว่าคุณได้ error อะไร!
หมายเหตุว่า เพราะ back_of_house::Breakfast มี field private struct ต้อง
ให้ associated function public ที่สร้าง instance ของ Breakfast (เรา
ตั้งชื่อมัน summer ที่นี่) ถ้า Breakfast ไม่มีฟังก์ชันแบบนี้ เราสร้าง
instance ของ Breakfast ใน eat_at_restaurant ไม่ได้ เพราะเรา set ค่า
ของ field seasonal_fruit private ใน eat_at_restaurant ไม่ได้
ตรงข้าม ถ้าเราทำให้ enum เป็น public variant ทั้งหมดของมันจะเป็น public
แล้ว เราต้องการแค่ pub ก่อน keyword enum ดังที่แสดงใน Listing 7-10
mod back_of_house {
pub enum Appetizer {
Soup,
Salad,
}
}
pub fn eat_at_restaurant() {
let order1 = back_of_house::Appetizer::Soup;
let order2 = back_of_house::Appetizer::Salad;
}
เพราะเราทำให้ enum Appetizer เป็น public เราใช้ variant Soup และ
Salad ใน eat_at_restaurant ได้
Enum ไม่มีประโยชน์มากเว้นแต่ variant ของมันเป็น public มันจะน่ารำคาญที่
ต้อง annotate variant ของ enum ทั้งหมดด้วย pub ในทุกกรณี default
สำหรับ variant ของ enum จึงเป็น public Struct มักมีประโยชน์โดยที่ field
ไม่ public ดังนั้น field ของ struct ตามกฎทั่วไปที่ทุกอย่างเป็น private
โดย default เว้นแต่ annotate ด้วย pub
มีอีกสถานการณ์ที่เกี่ยวกับ pub ที่เราไม่ได้ครอบคลุม และนั่นคือฟีเจอร์
ระบบ module สุดท้ายของเรา — keyword use เราจะครอบคลุม use เอง ๆ ก่อน
แล้วเราจะแสดงวิธีรวม pub และ use