ควบคุมขอบเขตและความเป็นส่วนตัวด้วยโมดูล (Control Scope and Privacy with Modules)
ในส่วนนี้ เราจะพูดถึงโมดูลและส่วนอื่น ๆ ของระบบโมดูล ได้แก่ เส้นทาง (paths) ซึ่งช่วยให้คุณสามารถระบุชื่อของไอเท็มต่าง ๆ ได้, คีย์เวิร์ด use สำหรับนำเอาเส้นทางเข้าสู่ขอบเขตการทำงาน และคีย์เวิร์ด pub เพื่อกำหนดให้ไอเท็มนั้น ๆ เป็นแบบสาธารณะ นอกจากนี้เรายังจะอภิปรายเกี่ยวกับคีย์เวิร์ด as, แพ็กเกจภายนอก (external packages) และตัวดำเนินการดอกจัน (glob operator) อีกด้วย
สรุปย่อการใช้งานโมดูล (Modules Cheat Sheet)
ก่อนที่เราจะลงลึกถึงรายละเอียดของโมดูลและเส้นทาง เราขอสรุปข้อมูลอ้างอิงอย่างรวดเร็วเกี่ยวกับวิธีการทำงานของโมดูล, เส้นทาง, คีย์เวิร์ด use และคีย์เวิร์ด pub ในคอมไพเลอร์ รวมถึงวิธีการจัดระเบียบโค้ดที่นักพัฒนาส่วนใหญ่นิยมใช้ เราจะศึกษาตัวอย่างของกฎแต่ละข้อในบทนี้ แต่หัวข้อนี้จะเป็นจุดอ้างอิงที่ดีในการช่วยเตือนความจำเกี่ยวกับหลักการทำงานของโมดูล
- เริ่มต้นจากรากเครต (crate root): เมื่อทำการคอมไพล์เครต ตัวคอมไพเลอร์จะมองหาไฟล์รากเครตเป็นอันดับแรกสุดเพื่อคอมไพล์โค้ด (โดยทั่วไปคือไฟล์ src/lib.rs สำหรับเครตห้องสมุด และไฟล์ src/main.rs สำหรับเครตฐานสอง)
- การประกาศโมดูล: ในไฟล์รากเครต คุณสามารถเขียนประกาศโมดูลใหม่ได้ เช่น การประกาศโมดูลสวน “garden” ด้วยคำสั่ง
mod garden;ทางคอมไพเลอร์จะทำการค้นหาโค้ดของโมดูลดังกล่าวในสถานที่เหล่านี้:- เขียนแบบอินไลน์ (inline) ภายในวงเล็บปีกกาที่เขียนขึ้นมาแทนเครื่องหมายอัฒภาค (semicolon) ต่อจากคำว่า
mod garden - ภายในไฟล์ src/garden.rs
- ภายในไฟล์ src/garden/mod.rs
- เขียนแบบอินไลน์ (inline) ภายในวงเล็บปีกกาที่เขียนขึ้นมาแทนเครื่องหมายอัฒภาค (semicolon) ต่อจากคำว่า
- การประกาศโมดูลย่อย (submodules): ในไฟล์อื่น ๆ ที่ไม่ใช่ไฟล์รากเครต คุณสามารถประกาศโมดูลย่อยได้ ตัวอย่างเช่น คุณอาจเขียนประกาศคำสั่ง
mod vegetables;ไว้ในไฟล์ src/garden.rs คอมไพเลอร์จะทำการค้นหาโค้ดของโมดูลย่อยดังกล่าวภายในไดเรกทอรีที่มีชื่อเดียวกับโมดูลหลัก (parent module) ในสถานที่เหล่านี้:- เขียนแบบอินไลน์ต่อจากคำว่า
mod vegetablesโดยใช้วงเล็บปีกกาแทนที่เครื่องหมายอัฒภาค - ภายในไฟล์ src/garden/vegetables.rs
- ภายในไฟล์ src/garden/vegetables/mod.rs
- เขียนแบบอินไลน์ต่อจากคำว่า
- เส้นทางไปยังโค้ดในโมดูล: เมื่อโมดูลเข้ามาเป็นส่วนหนึ่งของเครตของคุณแล้ว คุณจะสามารถอ้างอิงถึงโค้ดในโมดูลนั้นจากจุดใด ๆ ในเครตเดียวกันได้ ตราบใดที่กฎความเป็นส่วนตัวอนุญาต โดยใช้เส้นทาง (path) ไปยังโค้ดนั้น ตัวอย่างเช่น ชนิดข้อมูล
Asparagusในโมดูลผักสวนครัวจะเข้าถึงได้ทางเส้นทางcrate::garden::vegetables::Asparagus - ความเป็นส่วนตัวและสาธารณะ (Private vs. public): โดยปกติแล้ว โค้ดที่อยู่ภายในโมดูลจะเป็นแบบส่วนตัว (private) จากโมดูลหลัก (parent modules) เสมอ หากต้องการกำหนดให้โมดูลเป็นแบบสาธารณะ ให้ใช้คำสั่งประกาศเป็น
pub modแทนคำว่าmodและหากต้องการให้ไอเท็มภายในโมดูลสาธารณะเป็นแบบสาธารณะด้วย ให้ใส่คีย์เวิร์ดpubไว้หน้าคำประกาศของไอเท็มเหล่านั้น - คีย์เวิร์ด
use: ภายในขอบเขตการทำงาน คีย์เวิร์ดuseจะใช้สร้างทางลัด (shortcuts) ไปยังไอเท็มเพื่อลดการเขียนเส้นทางยาว ๆ ซ้ำไปซ้ำมา ในขอบเขตใด ๆ ที่สามารถเข้าถึงcrate::garden::vegetables::Asparagusได้ คุณสามารถสร้างทางลัดได้ด้วยคำสั่งuse crate::garden::vegetables::Asparagus;และหลังจากนั้นคุณเพียงแค่พิมพ์คำว่าAsparagusเพื่อใช้งานชนิดข้อมูลนั้นภายในขอบเขตได้เลย
ด้านล่างนี้ เราได้สร้างเครตฐานสองชื่อ backyard เพื่อแสดงหลักการของกฎเหล่านี้ ไดเรกทอรีของเครตซึ่งใช้ชื่อว่า backyard เช่นกัน จะบรรจุไฟล์และไดเรกทอรีต่อไปนี้:
backyard
├── Cargo.lock
├── Cargo.toml
└── src
├── garden
│ └── vegetables.rs
├── garden.rs
└── main.rs
ไฟล์รากเครตในกรณีนี้คือ src/main.rs ซึ่งมีข้อมูลภายในดังนี้:
use crate::garden::vegetables::Asparagus;
pub mod garden;
fn main() {
let plant = Asparagus {};
println!("I'm growing {plant:?}!");
}
บรรทัด pub mod garden; จะทำหน้าที่แจ้งให้คอมไพเลอร์นำโค้ดที่พบในไฟล์ src/garden.rs เข้ามารวม ซึ่งโค้ดดังกล่าวคือ:
pub mod vegetables;
ในที่นี้ pub mod vegetables; หมายถึงการนำโค้ดในไฟล์ src/garden/vegetables.rs เข้ามารวมด้วยเช่นกัน ซึ่งโค้ดนั้นคือ:
#[derive(Debug)]
pub struct Asparagus {}
คราวนี้เรามาเจาะลึกถึงรายละเอียดของกฎเหล่านี้และสัมผัสการทำงานจริงกันเลย!
การจัดกลุ่มโค้ดที่เกี่ยวข้องกันในโมดูล (Grouping Related Code in Modules)
โมดูล (Modules) ช่วยให้เราสามารถจัดระเบียบโค้ดภายในเครตเพื่อให้อ่านเข้าใจง่ายและนำกลับมาใช้ใหม่ได้สะดวก นอกจากนี้โมดูลยังช่วยควบคุม ความเป็นส่วนตัว (privacy) ของไอเท็มต่าง ๆ ได้ เนื่องจากโดยปกติแล้ว โค้ดที่อยู่ภายในโมดูลจะเป็นแบบส่วนตัว (private) เสมอ ไอเท็มส่วนตัวจัดเป็นรายละเอียดการทำงานภายในที่ไม่มีให้สำหรับภายนอกนำไปใช้งานต่อ แต่เราสามารถเลือกที่จะทำให้โมดูลรวมถึงไอเท็มที่อยู่ภายในเป็นแบบสาธารณะ (public) ได้ ซึ่งจะเป็นการเปิดเผยเพื่อให้โค้ดภายนอกสามารถเรียกใช้และอ้างอิงเพื่อนำไปเชื่อมโยงใช้ต่อได้
ตัวอย่างเช่น เรามาลองเขียนเครตห้องสมุดที่ให้บริการระบบของร้านอาหาร โดยเราจะระบุเพียงหัวประกาศ (signatures) ของฟังก์ชันต่าง ๆ แต่จะเว้นว่างเนื้อตัวฟังก์ชัน (bodies) ไว้ก่อน เพื่อที่เราจะได้ให้ความสำคัญกับการจัดระเบียบโครงสร้างโค้ด แทนที่จะเป็นรายละเอียดการทำงานจริงของร้านอาหาร
ในธุรกิจร้านอาหาร ชิ้นส่วนบางแห่งของร้านจะเรียกว่าส่วนหน้า (front of house) และส่วนอื่น ๆ จะเรียกว่าส่วนหลัง (back of house) โดย ส่วนหน้า (Front of house) คือส่วนที่ลูกค้าเข้ามาใช้บริการ ซึ่งครอบคลุมจุดต้อนรับลูกค้าที่พนักงานพาไปนั่งโต๊ะ พนักงานเสิร์ฟรับออเดอร์และเก็บเงิน และบาร์เทนเดอร์ผสมเครื่องดื่ม ส่วน ส่วนหลัง (Back of house) คือจุดที่เชฟและกุ๊กปรุงอาหารในครัว พนักงานล้างจานทำความสะอาด และผู้จัดการทำงานเอกสารธุรการ
เพื่อจัดระเบียบเครตของเราในรูปแบบนี้ เราสามารถจัดกลุ่มฟังก์ชันต่าง ๆ ให้อยู่ในรูปแบบโมดูลย่อยซ้อนกันได้ ให้คุณสร้างห้องสมุดใหม่ที่ชื่อว่า restaurant โดยรันคำสั่ง cargo new restaurant --lib จากนั้นนำโค้ดในรายการโค้ดที่ 7-1 ไปบันทึกในไฟล์ src/lib.rs เพื่อกำหนดโมดูลและหัวประกาศของฟังก์ชันต่าง ๆ ซึ่งโค้ดชุดนี้คือส่วนหน้าของร้านอาหาร
mod front_of_house {
mod hosting {
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
เรากำหนดโมดูลด้วยการพิมพ์คีย์เวิร์ด mod ตามด้วยชื่อของโมดูล (ในกรณีนี้คือ front_of_house) เนื้อความภายในโมดูลจะเขียนอยู่ภายในวงเล็บปีกกา ภายในโมดูลเราสามารถนำเอาโมดูลอื่น ๆ ไปเขียนบรรจุไว้ได้ ดังเช่นกรณีนี้ที่มีโมดูล hosting และ serving นอกจากนี้โมดูลยังสามารถจัดเก็บคำประกาศสำหรับไอเท็มอื่น ๆ ได้ เช่น structs, enums, ค่าคงที่, traits และรวมถึงฟังก์ชันต่าง ๆ เช่นที่ทำในรายการโค้ดที่ 7-1
การใช้ประโยชน์จากโมดูลช่วยให้เราจัดกลุ่มคำนิยามต่าง ๆ ที่เกี่ยวข้องกันเข้าไว้ด้วยกัน และสามารถตั้งชื่ออธิบายสาเหตุความเกี่ยวข้องกันนั้นได้ นักพัฒนาท่านอื่นที่นำโค้ดนี้ไปประมวลผลต่อก็จะสามารถค้นหาและไล่สายตาดูโค้ดตามกลุ่มที่กำหนดได้ แทนที่จะต้องอ่านเอกสารรายละเอียดทั้งหมด ช่วยให้ค้นหาฟังก์ชันการทำงานที่ต้องการได้ง่ายขึ้น นอกจากนี้ นักพัฒนาที่ต้องการเพิ่มฟีเจอร์การทำงานใหม่ ๆ ลงในโค้ดนี้ก็จะทราบถึงจุดที่เหมาะสมในการเขียนเพิ่มเติมเพื่อรักษาความเป็นระเบียบเรียบร้อยของโปรแกรม
ก่อนหน้านี้เราได้พูดถึงไปว่าไฟล์ src/main.rs และ src/lib.rs จะถูกเรียกว่า รากเครต (crate roots) เหตุผลที่เรียกแบบนี้เพราะเนื้อหาข้อมูลใด ๆ ภายในไฟล์ทั้งสองไฟล์นี้จะทำหน้าที่สร้างโมดูลชื่อ crate ที่อยู่บริเวณฐานรากของโครงสร้างโมดูลของเครต หรือที่เรียกว่า ต้นไม้โมดูล (module tree)
รายการโค้ดที่ 7-2 แสดงโครงสร้างต้นไม้โมดูลสำหรับนิยามในรายการโค้ดที่ 7-1
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
โครงสร้างต้นไม้นี้แสดงถึงวิธีการที่บางโมดูลซ้อนอยู่ภายในโมดูลอื่น ๆ ตัวอย่างเช่น hosting ซ้อนอยู่ใน front_of_house และยังแสดงว่าบางโมดูลมีสถานะเป็น พี่น้อง (siblings) กัน ซึ่งหมายความว่าพวกมันถูกกำหนดไว้ภายใต้โมดูลหลักตัวเดียวกัน โดย hosting และ serving เป็นพี่น้องกันที่ถูกนิยามไว้ใน front_of_house หากโมดูล A อยู่ภายในโมดูล B เราจะกล่าวว่าโมดูล A เป็น ลูก (child) ของโมดูล B และโมดูล B เป็น หลัก (parent) ของโมดูล A โปรดสังเกตว่าต้นไม้โมดูลทั้งหมดจะหยั่งรากอยู่ภายใต้โมดูลสมมติ (implicit module) ที่ชื่อว่า crate
ต้นไม้โมดูลนี้อาจทำให้คุณนึกถึงแผนผังไดเรกทอรีของระบบไฟล์ในคอมพิวเตอร์ของคุณ ซึ่งเป็นการเปรียบเทียบที่ตรงประเด็นและชัดเจนมาก! เช่นเดียวกับไดเรกทอรีในระบบไฟล์ คุณจะใช้โมดูลต่าง ๆ ในการจัดระเบียบโครงสร้างโค้ดของคุณ และทำนองเดียวกับที่ไฟล์ต้องอยู่ในไดเรกทอรี เราจึงจำเป็นต้องเรียนรู้วิธีการที่จะค้นพบโมดูลของเรา