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

เส้นทางสำหรับอ้างอิงถึงไอเท็มในต้นไม้โมดูล (Paths for Referring to an Item in the Module Tree)

ในการชี้บอก Rust ว่าจะค้นหาไอเท็มในโครงสร้างต้นไม้โมดูลได้จากที่ไหน เราจะใช้วิธีระบุเส้นทาง (path) ในลักษณะเดียวกับการนำทางระบบไฟล์ในคอมพิวเตอร์ของคุณ โดยการเรียกใช้งานฟังก์ชันใด ๆ เราจำเป็นต้องทราบเส้นทางของมัน

เส้นทางสามารถมีรูปแบบได้สองแบบ ได้แก่:

  • เส้นทางแบบสัมบูรณ์ (absolute path): คือเส้นทางแบบเต็มที่เริ่มต้นจากรากเครต (crate root) สำหรับโค้ดที่มาจากเครตภายนอก เส้นทางแบบสัมบูรณ์จะขึ้นต้นด้วยชื่อเครตนั้น ๆ และสำหรับโค้ดที่อยู่ในเครตปัจจุบัน จะขึ้นต้นด้วยคำว่า crate
  • เส้นทางแบบสัมพัทธ์ (relative path): คือเส้นทางที่เริ่มต้นจากโมดูลปัจจุบัน โดยใช้คำว่า self, super หรือตัวระบุชื่อที่อยู่ภายในโมดูลปัจจุบัน

ทั้งเส้นทางแบบสัมบูรณ์และแบบสัมพัทธ์จะถูกต่อท้ายด้วยตัวระบุชื่อหนึ่งตัวหรือมากกว่า ซึ่งคั่นกลางด้วยเครื่องหมายโคลอนคู่ (::)

ย้อนกลับไปพิจารณารายการโค้ดที่ 7-1 สมมติว่าเราต้องการเรียกใช้งานฟังก์ชัน add_to_waitlist ซึ่งก็เทียบเท่ากับคำถามที่ว่า: เส้นทางของฟังก์ชัน add_to_waitlist คืออะไร? รายการโค้ดที่ 7-3 คือโค้ดในรายการโค้ดที่ 7-1 ที่ได้ตัดโมดูลและฟังก์ชันบางส่วนที่ไม่เกี่ยวข้องออกไป

เราจะแสดงสองวิธีในการเรียกใช้งานฟังก์ชัน add_to_waitlist จากฟังก์ชันใหม่ที่ชื่อว่า eat_at_restaurant ซึ่งถูกกำหนดไว้บริเวณรากเครต เส้นทางเหล่านี้ถูกต้องดีแล้ว แต่ทว่ายังมีปัญหาอีกจุดหนึ่งที่จะขัดขวางไม่ให้โค้ดตัวอย่างนี้คอมไพล์ผ่านได้ทันที ซึ่งเราจะอธิบายสาเหตุในอีกสักครู่

ฟังก์ชัน eat_at_restaurant เป็นส่วนหนึ่งของ API สาธารณะของเครตห้องสมุดของเรา ดังนั้นเราจึงทำเครื่องหมายมันไว้ด้วยคีย์เวิร์ด pub โดยเราจะเจาะลึกรายละเอียดเกี่ยวกับ 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 ภายใน eat_at_restaurant เราได้เลือกใช้เส้นทางแบบสัมบูรณ์ เนื่องจากฟังก์ชัน add_to_waitlist ถูกกำหนดไว้ภายในเครตเดียวกับ eat_at_restaurant ทำให้เราสามารถใช้คีย์เวิร์ด crate เพื่อเริ่มต้นเขียนเส้นทางแบบสัมบูรณ์ได้ จากนั้นเราจะใส่รายชื่อโมดูลลำดับถัดไปทีละตัวจนกระทั่งเดินทางไปถึง add_to_waitlist คุณสามารถจินตนาการเทียบกับระบบไฟล์ที่มีโครงสร้างเหมือนกันได้ว่า เราจะต้องระบุเส้นทางเป็น /front_of_house/hosting/add_to_waitlist เพื่อรันโปรแกรม add_to_waitlist การใช้ชื่อ crate เพื่อเริ่มต้นจากรากเครตจึงเปรียบเสมือนการใช้เครื่องหมาย / เพื่อเริ่มต้นระบุจากรากของระบบไฟล์ในเชลล์ของคุณ

ในครั้งที่สองที่เราเรียกใช้ฟังก์ชัน add_to_waitlist ใน eat_at_restaurant เราใช้เส้นทางแบบสัมพัทธ์ โดยเริ่มเขียนจาก front_of_house ซึ่งเป็นชื่อโมดูลที่ประกาศไว้ในระดับเดียวกันกับ eat_at_restaurant บนต้นไม้โมดูล ในที่นี้หากเปรียบเทียบกับระบบไฟล์จะเทียบเท่ากับการใช้เส้นทาง front_of_house/hosting/add_to_waitlist การเขียนเริ่มต้นด้วยชื่อโมดูลจึงสื่อความหมายว่าเส้นทางนั้นเป็นแบบสัมพัทธ์

การตัดสินใจว่าจะเลือกใช้เส้นทางแบบสัมพัทธ์หรือแบบสัมบูรณ์เป็นเรื่องที่คุณต้องพิจารณาตามลักษณะของโปรเจกต์ ซึ่งขึ้นอยู่กับว่าคุณมีแนวโน้มที่จะย้ายโค้ดส่วนที่ประกาศนิยามไอเท็มแยกออกจากกัน หรือย้ายไปพร้อม ๆ กันกับโค้ดส่วนที่เรียกใช้ไอเท็มนั้น ตัวอย่างเช่น หากเราตัดสินใจย้ายโมดูล front_of_house และฟังก์ชัน eat_at_restaurant เข้าไปอยู่ภายใต้โมดูลใหม่ชื่อ customer_experience เราจำเป็นจะต้องเข้าไปแก้ไขปรับปรุงเส้นทางแบบสัมบูรณ์ของ add_to_waitlist แต่ทว่าเส้นทางแบบสัมพัทธ์จะยังคงใช้งานได้ปกติโดยไม่ต้องแก้ไขอะไรเลย แต่ในทางกลับกัน หากเราย้ายเฉพาะฟังก์ชัน eat_at_restaurant แยกออกมาไว้ในโมดูลชื่อ dining เส้นทางแบบสัมบูรณ์ของการเรียก add_to_waitlist จะยังคงเดิมไม่เปลี่ยนแปลง แต่เราจำเป็นต้องเข้าไปอัปเดตเส้นทางแบบสัมพัทธ์แทน โดยทั่วไปแล้วความนิยมส่วนตัวของเราคือการระบุเส้นทางแบบสัมบูรณ์ เนื่องจากมีโอกาสมากกว่าที่เราจะย้ายตำแหน่งการประกาศโค้ดและการเรียกใช้ไอเท็มอย่างเป็นอิสระต่อกัน

เรามาลองคอมไพล์โค้ดในรายการโค้ดที่ 7-3 เพื่อหาสาเหตุว่าทำไมมันถึงยังคอมไพล์ไม่ผ่านกัน! ข้อผิดพลาดที่ได้รับแสดงในรายการโค้ดที่ 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

ข้อความแจ้งข้อผิดพลาดระบุว่าโมดูล hosting เป็นแบบส่วนตัว (private) พูดอีกนัยหนึ่งคือ เราเขียนเส้นทางไปยังโมดูล hosting และฟังก์ชัน add_to_waitlist ได้อย่างถูกต้องดีแล้ว แต่ทว่า Rust ไม่อนุญาตให้เราใช้งานพวกมันเนื่องจากระบบไม่ได้รับสิทธิ์ให้เข้าถึงส่วนที่เป็นส่วนตัว ในภาษา Rust ไอเท็มทั้งหมด (ฟังก์ชัน, เมธอด, structs, enums, โมดูล และค่าคงที่) จะถูกตั้งค่าเริ่มต้นให้เป็นแบบส่วนตัวจากโมดูลหลักเสมอ หากคุณต้องการกำหนดให้ไอเท็มอย่างฟังก์ชันหรือ struct เป็นแบบส่วนตัว คุณก็เพียงนำมันไปเก็บไว้ในโมดูล

ไอเท็มที่อยู่ในโมดูลหลักจะไม่สามารถใช้งานไอเท็มส่วนตัวที่อยู่ภายในโมดูลย่อย (child modules) ได้ แต่ไอเท็มในโมดูลย่อยจะสามารถเรียกใช้งานไอเท็มที่ประกาศไว้ในโมดูลบรรพบุรุษ (ancestor modules) ของพวกมันได้ ที่เป็นเช่นนี้เพราะโมดูลย่อยมีหน้าที่ห่อหุ้มและปกปิดรายละเอียดการทำงานภายใน แต่โมดูลย่อยเหล่านั้นยังมองเห็นบริบทแวดล้อมที่มันถูกนิยามขึ้นมาได้ หากใช้การเปรียบเทียบกับร้านอาหารเดิม ให้จินตนาการว่ากฎความเป็นส่วนตัวเปรียบเสมือนห้องผู้จัดการหลังร้าน ซึ่งสิ่งที่เกิดขึ้นในนั้นจะเป็นเรื่องส่วนตัวที่ลูกค้าของร้านไม่สามารถรับรู้ได้ แต่ผู้จัดการร้านสามารถมองเห็นและทำทุกอย่างในร้านอาหารที่เขาดูแลได้

Rust เลือกที่จะกำหนดให้ระบบโมดูลทำงานในลักษณะนี้เพื่อให้การปกปิดรายละเอียดการทำงานภายในเป็นค่าเริ่มต้น ซึ่งช่วยให้คุณรู้แน่ชัดว่าโค้ดส่วนภายในส่วนใดที่คุณสามารถปรับเปลี่ยนแก้ไขได้โดยไม่ส่งผลเสียให้โค้ดส่วนภายนอกพังเสียหาย แต่อย่างไรก็ตาม Rust ก็มีตัวเลือกให้คุณสามารถเปิดเผยโค้ดภายในของโมดูลย่อยให้โมดูลบรรพบุรุษภายนอกเห็นได้ โดยการใช้คีย์เวิร์ด pub เพื่อกำหนดให้ไอเท็มนั้นเป็นแบบสาธารณะ

เปิดเผยเส้นทางด้วยคีย์เวิร์ด pub (Exposing Paths with the pub Keyword)

เรามาย้อนกลับมาพิจารณาข้อผิดพลาดในรายการโค้ดที่ 7-4 ที่แจ้งว่าโมดูล hosting เป็นแบบส่วนตัวกัน เราต้องการให้ฟังก์ชัน eat_at_restaurant ในโมดูลหลักมีสิทธิ์เข้าถึงฟังก์ชัน add_to_waitlist ในโมดูลย่อยได้ เราจึงเขียนคีย์เวิร์ด pub กำกับไว้หน้าโมดูล hosting ดังแสดงในรายการโค้ดที่ 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();
}

แต่น่าเสียดายที่โค้ดในรายการโค้ดที่ 7-5 ก็ยังคงส่งผลให้เกิดข้อผิดพลาดในการคอมไพล์อยู่ดี ดังแสดงในรายการโค้ดที่ 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

เกิดอะไรขึ้น? การใส่คีย์เวิร์ด pub ไว้หน้า mod hosting ช่วยกำหนดให้ตัวโมดูลเป็นแบบสาธารณะ ซึ่งจากการเปลี่ยนแปลงนี้ หากเรามีสิทธิ์เข้าถึง front_of_house เราก็จะมีสิทธิ์เข้าถึง hosting ด้วย แต่ทว่า เนื้อหาภายใน ของ hosting ยังคงเป็นแบบส่วนตัวอยู่ดี การกำหนดให้โมดูลเป็นสาธารณะไม่ได้ส่งผลให้เนื้อหาข้อมูลด้านในกลายเป็นสาธารณะตามไปด้วย คีย์เวิร์ด pub บนโมดูลเพียงอนุญาตให้โค้ดในโมดูลบรรพบุรุษอ้างอิงมาถึงตัวโมดูลได้เท่านั้น แต่ไม่ได้อนุญาตให้เจาะเข้าไปใช้โค้ดด้านใน เนื่องจากโมดูลเปรียบเสมือนกล่องคอนเทนเนอร์ การกำหนดให้เฉพาะตัวกล่องเป็นสาธารณะจึงยังทำอะไรได้ไม่มากนัก เราจำเป็นต้องก้าวไปอีกขั้นโดยเลือกกำหนดให้ไอเท็มชิ้นหนึ่งหรือหลายชิ้นที่อยู่ในโมดูลนั้นกลายเป็นสาธารณะด้วย

ข้อผิดพลาดในรายการโค้ดที่ 7-6 ระบุว่าฟังก์ชัน add_to_waitlist เป็นแบบส่วนตัว ซึ่งกฎความเป็นส่วนตัวนี้จะบังคับใช้กับทั้ง structs, enums, ฟังก์ชัน, เมธอด และโมดูลเช่นเดียวกัน

เรามาทำให้ฟังก์ชัน add_to_waitlist กลายเป็นสาธารณะด้วยการเพิ่มคีย์เวิร์ด pub ไว้หน้าคำจำกัดความของมันกันเถอะ ดังแสดงในรายการโค้ดที่ 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 จึงทำให้เราสามารถใช้เส้นทางเหล่านี้ใน eat_at_restaurant ได้โดยสอดคล้องกับกฎความเป็นส่วนตัว เรามาลองดูการทำงานของเส้นทางแบบสัมบูรณ์และแบบสัมพัทธ์กัน

สำหรับเส้นทางแบบสัมบูรณ์เราเริ่มต้นจาก crate ซึ่งเป็นรากของโครงสร้างต้นไม้โมดูลของเครต โมดูล front_of_house ถูกนิยามไว้ที่ระดับรากเครต แม้ว่า front_of_house จะไม่เป็นแบบสาธารณะ แต่เนื่องจากฟังก์ชัน eat_at_restaurant ถูกประกาศไว้ในโมดูลเดียวกับ front_of_house (นั่นคือ eat_at_restaurant และ front_of_house มีสถานะเป็นพี่น้องกัน) เราจึงอ้างอิงถึง front_of_house จาก eat_at_restaurant ได้ ถัดไปคือโมดูล hosting ซึ่งทำเครื่องหมายด้วย pub เนื่องจากเรามีสิทธิ์เข้าถึงโมดูลหลักของ hosting ได้อยู่แล้ว เราจึงเข้าถึง hosting ได้ และสุดท้ายฟังก์ชัน add_to_waitlist ถูกกำหนดด้วย pub และเราเข้าถึงโมดูลหลักของมันได้เช่นกัน การเรียกใช้งานฟังก์ชันจุดนี้จึงทำงานได้สำเร็จ!

ในเส้นทางแบบสัมพัทธ์ ตรรกะการวิเคราะห์จะคล้ายกับเส้นทางแบบสัมบูรณ์ ยกเว้นขั้นตอนแรกสุดที่แทนที่จะเริ่มค้นหาจากรากเครต จะเริ่มค้นหาจากโมดูล front_of_house แทน เนื่องจากโมดูล front_of_house ประกาศไว้ในระดับเดียวกันกับ eat_at_restaurant เส้นทางแบบสัมพัทธ์ที่เริ่มจากโมดูลที่เป็นที่อยู่ของ eat_at_restaurant จึงทำงานได้ จากนั้นเพราะ hosting และ add_to_waitlist ถูกกำหนดด้วยคีย์เวิร์ด pub เส้นทางที่เหลือจึงถูกต้อง และทำให้การเรียกใช้ฟังก์ชันนี้เสร็จสิ้นอย่างสมบูรณ์!

หากคุณมีแผนที่จะแบ่งปันเครตห้องสมุดของคุณเพื่อโปรเจกต์อื่น ๆ จะได้นำโค้ดไปใช้ต่อ API สาธารณะของคุณจะเปรียบเสมือนสัญญาข้อตกลงกับผู้ใช้เครตของคุณ ซึ่งกำหนดแนวทางที่พวกเขาสามารถโต้ตอบทำงานร่วมกับโค้ดของคุณได้ การจัดการความเปลี่ยนแปลงของ API สาธารณะเพื่อให้นำไปใช้งานอ้างอิงต่อได้ง่ายนั้นมีข้อพิจารณาปลีกย่อยมากมาย ซึ่งข้อคิดเห็นเหล่านั้นอยู่นอกเหนือขอบเขตของหนังสือเล่มนี้ หากคุณมีความสนใจใคร่รู้ในหัวข้อดังกล่าว สามารถเปิดดูรายละเอียดได้ในคู่มือแนวทางการออกแบบ Rust API (Rust API Guidelines)

แนวทางปฏิบัติที่ดีที่สุดสำหรับแพ็กเกจที่มีทั้งไฟล์ระบบฐานสองและห้องสมุด (Best Practices for Packages with a Binary and a Library)

เรามีระบุไปว่าแพ็กเกจหนึ่งสามารถมีทั้งรากเครตฐานสองอย่าง src/main.rs และรากเครตห้องสมุดอย่าง src/lib.rs ควบคู่กันได้ และเครตทั้งสองจะใช้ชื่อร่วมตามชื่อแพ็กเกจเป็นค่าเริ่มต้น โดยทั่วไป แพ็กเกจที่ใช้โครงสร้างแบบมีทั้งห้องสมุดและไฟล์ประมวลผลนี้ จะเน้นเขียนโค้ดในเครตฐานสองไว้เพียงเล็กน้อยเฉพาะเท่าที่จำเป็นสำหรับรันเป็นไฟล์ประมวลผลที่จะทำหน้าที่กระโดดไปเรียกใช้โค้ดที่กำหนดไว้ในเครตห้องสมุดอีกทีหนึ่ง วิธีนี้ช่วยให้โปรเจกต์อื่น ๆ ได้รับประโยชน์สูงสุดจากฟังก์ชันที่แพ็กเกจนำเสนอ เนื่องจากโค้ดของเครตห้องสมุดจะสามารถนำไปแชร์ใช้งานร่วมกันได้ง่าย

โครงสร้างต้นไม้โมดูลควรได้รับการประกาศนิยามไว้ในไฟล์ src/lib.rs หลังจากนั้น ไอเท็มสาธารณะใด ๆ ก็จะสามารถถูกเรียกใช้ภายในเครตฐานสองได้โดยระบุเส้นทางเริ่มต้นด้วยชื่อของแพ็กเกจ ซึ่งเครตฐานสองจะมีสถานะเป็นเสมือนผู้ใช้งานเครตห้องสมุดทั่วไป เช่นเดียวกับเครตภายนอกอื่น ๆ ที่จะเข้ามาขอดึงห้องสมุดนี้ไปใช้ คือมีสิทธิ์เรียกใช้งานได้เฉพาะส่วนของ API สาธารณะเท่านั้น ซึ่งจุดนี้จะช่วยให้คุณออกแบบ API ที่ดีได้ เพราะนอกจากคุณจะเป็นผู้เขียนแล้ว คุณยังเป็นลูกค้าผู้ใช้งานเองด้วย!

ในบทที่ 12 เราจะสาธิตการจัดระเบียบรูปแบบนี้ผ่านโปรแกรมแบบบรรทัดคำสั่ง (CLI) ที่จะประกอบไปด้วยทั้งเครตฐานสองและเครตห้องสมุดร่วมกัน

การเขียนเริ่มต้นเส้นทางแบบสัมพัทธ์ด้วย super (Starting Relative Paths with super)

เราสามารถเขียนเส้นทางแบบสัมพัทธ์ที่เริ่มจากโมดูลหลัก (parent module) แทนที่จะเริ่มจากโมดูลปัจจุบันหรือรากเครตได้ โดยการใส่คำว่า super ไว้ที่ด้านหน้าสุดของเส้นทาง สิ่งนี้เปรียบเสมือนการเขียนเส้นทางในระบบไฟล์ด้วยเครื่องหมาย .. เพื่อระบุให้ย้อนขึ้นไปที่ไดเรกทอรีด้านบน การใช้ super ช่วยให้เราอ้างอิงถึงไอเท็มที่เราทราบดีว่าถูกประกาศไว้ในโมดูลหลักได้ ซึ่งจะทำให้การปรับเปลี่ยนโครงสร้างต้นไม้โมดูลในอนาคตทำได้ง่ายขึ้น ในยามที่โมดูลย่อยมีความผูกพันใกล้ชิดกับโมดูลหลัก แต่โมดูลหลักนั้นอาจถูกโยกย้ายไปอยู่ที่ตำแหน่งอื่นในอนาคต

ลองพิจารณาโค้ดในรายการโค้ดที่ 7-8 ที่จำลองสถานการณ์ที่พ่อครัวตรวจพบความผิดพลาดของออเดอร์และทำหน้าที่แก้ไขออเดอร์นั้นพร้อมนำไปเสิร์ฟให้ลูกค้าด้วยตนเอง ฟังก์ชัน fix_incorrect_order ที่อยู่ในโมดูล back_of_house จะเรียกใช้ฟังก์ชัน deliver_order ที่อยู่ในโมดูลหลักภายนอก โดยระบุเส้นทางไปยัง deliver_order ที่เริ่มต้นด้วยคำว่า super

fn deliver_order() {}

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

    fn cook_order() {}
}

ฟังก์ชัน fix_incorrect_order อยู่ภายในโมดูล back_of_house ดังนั้นเราจึงสามารถใช้ super เพื่อถอยออกไปยังโมดูลหลักของ back_of_house ซึ่งในกรณีนี้คือ crate (รากเครต) และจากรากเครตนี้เราก็จะมองหาฟังก์ชัน deliver_order จนพบได้สำเร็จ! เราคาดการณ์ว่าโมดูล back_of_house และฟังก์ชัน deliver_order น่าจะมีความสัมพันธ์เชิงโครงสร้างแบบนี้อยู่เสมอ และจะถูกโยกย้ายไปด้วยกันหากมีการจัดระเบียบโครงสร้างต้นไม้โมดูลใหม่ ด้วยเหตุนี้การเลือกใช้ super จะช่วยลดจุดที่เราต้องเข้าไปแก้ปรับปรุงโค้ดในอนาคตได้หากมีการย้ายโค้ดชุดนี้ไปไว้ในโมดูลอื่น

การกำหนดให้ Struct และ Enum เป็นแบบสาธารณะ (Making Structs and Enums Public)

เราสามารถใช้ pub เพื่อกำหนดให้โครงสร้างข้อมูล (structs) และ enums กลายเป็นสาธารณะได้เช่นกัน แต่ทว่าจะมีรายละเอียดเฉพาะตัวสำหรับการใช้ pub ร่วมกับ structs และ enums อยู่บ้าง หากเราใช้ pub ไว้หน้าคำประกาศ struct จะส่งผลให้ตัว struct นั้นกลายเป็นสาธารณะ แต่ทว่าฟิลด์ (fields) ภายใน struct จะยังคงมีสถานะเป็นส่วนตัว (private) อยู่เหมือนเดิม โดยเราสามารถเลือกที่จะกำหนดให้แต่ละฟิลด์เป็นสาธารณะหรือไม่แยกเป็นกรณี ๆ ไปได้ ในรายการโค้ดที่ 7-9 เราได้กำหนด struct back_of_house::Breakfast แบบสาธารณะ โดยมีฟิลด์ toast เป็นสาธารณะ แต่ฟิลด์ seasonal_fruit เป็นส่วนตัว ซึ่งจำลองสถานการณ์ในร้านอาหารที่ลูกค้าสามารถสั่งระบุประเภทของขนมปังที่จะทานคู่กับมื้ออาหารได้ แต่พ่อครัวจะเป็นผู้ตัดสินใจว่าผลไม้ตามฤดูกาลชนิดใดที่จะนำมาเสิร์ฟให้ โดยอ้างอิงจากผลไม้ที่มีอยู่และวัตถุดิบในสต็อก เนื่องจากรายการผลไม้มีการเปลี่ยนแปลงได้ง่าย ลูกค้าจึงไม่สามารถเลือกผลไม้หรือมองเห็นก่อนได้ว่าจะได้ผลไม้อะไร

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");
}

เนื่องจากฟิลด์ toast ภายใน struct back_of_house::Breakfast เป็นแบบสาธารณะ ทำให้ในฟังก์ชัน eat_at_restaurant เราสามารถเขียนและอ่านข้อมูลของฟิลด์ toast ได้โดยตรงผ่านการเขียนระบุด้วยสัญลักษณ์จุด (.) ทั้งนี้สังเกตว่าเราไม่สามารถเข้าถึงฟิลด์ seasonal_fruit ใน eat_at_restaurant ได้ เนื่องจาก seasonal_fruit เป็นส่วนตัว ลองเปิดคอมเมนต์บรรทัดที่ทำการแก้ไขค่าของ seasonal_fruit ดูเพื่อตรวจสอบข้อผิดพลาดที่จะเกิดขึ้นได้!

นอกจากนี้ สังเกตด้วยว่าเพราะ struct back_of_house::Breakfast มีฟิลด์แบบส่วนตัวอยู่ภายใน ตัว struct จึงจำเป็นต้องจัดเตรียมฟังก์ชันที่เกี่ยวเนื่อง (associated function) แบบสาธารณะเพื่อช่วยทำหน้าที่สร้างอินสแตนซ์ของ Breakfast ขึ้นมา (โดยในที่นี้เราตั้งชื่อมันว่า summer) หาก Breakfast ไม่มีฟังก์ชันสำหรับช่วยสร้างดังกล่าว เราก็จะไม่สามารถสร้างอินสแตนซ์ของ Breakfast ภายใน eat_at_restaurant ได้เลย เพราะเราไม่มีสิทธิ์เข้าไปตั้งค่าเริ่มต้นของฟิลด์ seasonal_fruit ที่เป็นส่วนตัวนั่นเอง

ในทางตรงกันข้าม หากเรากำหนดให้อีนัม (enum) เป็นแบบสาธารณะ จะส่งผลให้รูปแบบย่อยทั้งหมดภายในอีนัมกลายเป็นสาธารณะไปด้วยโดยอัตโนมัติ โดยเราต้องการเพียงแค่คำว่า pub นำหน้าคีย์เวิร์ด enum เท่านั้น ดังแสดงในรายการโค้ดที่ 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;
}

เนื่องจากเรากำหนดให้อีนัม Appetizer เป็นแบบสาธารณะ เราจึงสามารถนำเอารูปแบบ Soup และ Salad ไปเรียกเขียนใช้งานต่อใน eat_at_restaurant ได้โดยไม่มีปัญหา

อีนัมจะไม่มีความหมายและนำไปใช้ได้ยากมากหากรูปแบบภายในของมันไม่เป็นสาธารณะ และการต้องมาคอยพิมพ์ pub กำกับในทุก ๆ รูปแบบย่อยของอีนัมก็น่าเบื่อและซ้ำซากเกินไป ดังนั้นค่าเริ่มต้นสำหรับรูปแบบย่อยของอีนัมจึงเป็นแบบสาธารณะเสมอ แต่สำหรับ structs บ่อยครั้งที่มันยังมีประโยชน์ได้โดยที่ฟิลด์ภายในไม่ต้องเป็นสาธารณะ ดังนั้นฟิลด์ของ struct จึงยังคงปฏิบัติตามกฎทั่วไปคือทุกอย่างเป็นส่วนตัวเป็นค่าเริ่มต้น จนกว่าจะมีการเขียนระบุคีย์เวิร์ด pub กำกับไว้

ยังคงมีอีกหนึ่งสถานการณ์ที่เกี่ยวข้องกับคีย์เวิร์ด pub ที่เรายังไม่ได้เรียนรู้ ซึ่งเชื่อมโยงกับฟีเจอร์สุดท้ายของระบบโมดูล นั่นคือ คีย์เวิร์ด use โดยเราจะมาทำความเข้าใจเกี่ยวกับ use อย่างเดี่ยว ๆ กันก่อน จากนั้นจึงศึกษาถึงวิธีการนำเอา pub และ use มารวมพลังใช้งานร่วมกัน