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

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

Filename: src/lib.rs
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();
}
Listing 7-3: เรียกฟังก์ชัน 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
Listing 7-4: Compiler error จากการ build โค้ดใน Listing 7-3

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

Filename: src/lib.rs
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();
}
Listing 7-5: ประกาศ module 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
Listing 7-6: Compiler error จากการ build โค้ดใน Listing 7-5

เกิดอะไรขึ้น? การเพิ่ม 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

Filename: src/lib.rs
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();
}
Listing 7-7: การเพิ่ม keyword 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

Filename: src/lib.rs
fn deliver_order() {}

mod back_of_house {
    fn fix_incorrect_order() {
        cook_order();
        super::deliver_order();
    }

    fn cook_order() {}
}
Listing 7-8: เรียกฟังก์ชันด้วย relative path ที่เริ่มด้วย 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 ตัดสินใจว่าผลไม้ตัวไหนคู่กับมื้อ ขึ้นกับว่าอะไรอยู่ในฤดูและในสต็อก ผลไม้ ที่มีเปลี่ยนเร็ว ลูกค้าจึงเลือกผลไม้ไม่ได้ และเห็นไม่ได้แม้จะได้ผลไม้ ตัวไหน

Filename: src/lib.rs
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");
}
Listing 7-9: struct ที่มี field public บางตัวและ private บางตัว

เพราะ 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

Filename: src/lib.rs
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;
}
Listing 7-10: การกำหนด enum เป็น public ทำให้ variant ทั้งหมดเป็น public

เพราะเราทำให้ 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