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

ควบคุมขอบเขตและความเป็นส่วนตัวด้วยโมดูล (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
  • การประกาศโมดูลย่อย (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 {}

คราวนี้เรามาเจาะลึกถึงรายละเอียดของกฎเหล่านี้และสัมผัสการทำงานจริงกันเลย!

โมดูล (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

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