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 เข้า Scope ด้วย Keyword use

การต้องเขียน path เพื่อเรียกฟังก์ชันรู้สึกไม่สะดวกและซ้ำซ้อนได้ ใน Listing 7-7 ไม่ว่าเราเลือก absolute หรือ relative path ไปยังฟังก์ชัน add_to_waitlist ทุกครั้งที่เราอยากเรียก add_to_waitlist เราต้องระบุ front_of_house และ hosting ด้วย โชคดี มีวิธีที่จะทำให้กระบวนการนี้ ง่ายขึ้น — เราสร้าง shortcut ของ path ด้วย keyword use ครั้งเดียว แล้ว ใช้ชื่อสั้นกว่าทุกที่อื่นใน scope ได้

ใน Listing 7-11 เรานำ module crate::front_of_house::hosting เข้าใน scope ของฟังก์ชัน eat_at_restaurant เพื่อให้เราต้องระบุแค่ hosting::add_to_waitlist เพื่อเรียกฟังก์ชัน add_to_waitlist ใน eat_at_restaurant

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
Listing 7-11: นำ module เข้า scope ด้วย use

การเพิ่ม use และ path ใน scope คล้ายกับการสร้าง symbolic link ใน filesystem ด้วยการเพิ่ม use crate::front_of_house::hosting ใน crate root hosting ตอนนี้เป็นชื่อ valid ใน scope นั้น เหมือนกับว่า module hosting ประกาศใน crate root Path ที่นำเข้า scope ด้วย use ก็เช็ค privacy ด้วย เหมือน path อื่นใด

หมายเหตุว่า use สร้าง shortcut เฉพาะสำหรับ scope ที่ use เกิดเท่านั้น Listing 7-12 ย้ายฟังก์ชัน eat_at_restaurant เข้า module ลูกใหม่ชื่อ customer ซึ่งจึงเป็น scope ต่างจาก statement use body ของฟังก์ชันจะ compile ไม่ผ่าน

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting;

mod customer {
    pub fn eat_at_restaurant() {
        hosting::add_to_waitlist();
    }
}
Listing 7-12: statement use ใช้ได้แค่ใน scope ที่มันอยู่

Compiler error แสดงว่า shortcut ใช้ไม่ได้แล้วภายใน module customer:

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `hosting`
  --> src/lib.rs:11:9
   |
11 |         hosting::add_to_waitlist();
   |         ^^^^^^^ use of unresolved module or unlinked crate `hosting`
   |
   = help: if you wanted to use a crate named `hosting`, use `cargo add hosting` to add it to your `Cargo.toml`
help: consider importing this module through its public re-export
   |
10 +     use crate::hosting;
   |

warning: unused import: `crate::front_of_house::hosting`
 --> src/lib.rs:7:5
  |
7 | use crate::front_of_house::hosting;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

For more information about this error, try `rustc --explain E0433`.
warning: `restaurant` (lib) generated 1 warning
error: could not compile `restaurant` (lib) due to 1 previous error; 1 warning emitted

สังเกตว่ายังมี warning ว่า use ไม่ถูกใช้แล้วใน scope ของมัน! ในการแก้ ปัญหานี้ ย้าย use เข้า module customer ด้วย หรืออ้างถึง shortcut ใน module พ่อด้วย super::hosting ภายใน module customer ลูก

สร้าง Path use ที่ Idiomatic

ใน Listing 7-11 คุณอาจสงสัยว่าทำไมเราระบุ use crate::front_of_house::hosting แล้วเรียก hosting::add_to_waitlist ใน eat_at_restaurant แทนการระบุ path use ตลอดจนถึงฟังก์ชัน add_to_waitlist เพื่อให้ผลเดียวกัน เหมือนใน Listing 7-13

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

use crate::front_of_house::hosting::add_to_waitlist;

pub fn eat_at_restaurant() {
    add_to_waitlist();
}
Listing 7-13: นำฟังก์ชัน add_to_waitlist เข้า scope ด้วย use ซึ่งไม่ idiomatic

แม้ทั้ง Listing 7-11 และ Listing 7-13 ทำงานเดียวกัน Listing 7-11 เป็น วิธี idiomatic ในการนำฟังก์ชันเข้า scope ด้วย use การนำ module พ่อของ ฟังก์ชันเข้า scope ด้วย use หมายความว่าเราต้องระบุ module พ่อเมื่อเรียก ฟังก์ชัน การระบุ module พ่อเมื่อเรียกฟังก์ชันทำให้ชัดเจนว่าฟังก์ชันไม่ ประกาศ local ขณะลดการเขียน path เต็มซ้ำ โค้ดใน Listing 7-13 ไม่ชัดเจนว่า add_to_waitlist ประกาศที่ไหน

ตรงข้าม เมื่อนำ struct, enum และ item อื่นด้วย use มัน idiomatic ที่จะ ระบุ path เต็ม Listing 7-14 แสดงวิธี idiomatic ในการนำ struct HashMap ของ standard library เข้า scope ของ binary crate

Filename: src/main.rs
use std::collections::HashMap;

fn main() {
    let mut map = HashMap::new();
    map.insert(1, 2);
}
Listing 7-14: นำ HashMap เข้า scope ในแบบ idiomatic

ไม่มีเหตุผลแข็งแรงเบื้องหลัง idiom นี้ — มันแค่ convention ที่เกิดขึ้น และคนคุ้นเคยกับการอ่านและเขียนโค้ด Rust ในแบบนี้

ข้อยกเว้นของ idiom นี้คือถ้าเรานำสอง item ที่มีชื่อเดียวกันเข้า scope ด้วย statement use เพราะ Rust ไม่อนุญาตอย่างนั้น Listing 7-15 แสดงวิธี นำสอง type Result ที่มีชื่อเดียวกันแต่ module พ่อต่างกันเข้า scope และ วิธีอ้างถึงพวกมัน

Filename: src/lib.rs
use std::fmt;
use std::io;

fn function1() -> fmt::Result {
    // --snip--
    Ok(())
}

fn function2() -> io::Result<()> {
    // --snip--
    Ok(())
}
Listing 7-15: การนำสอง type ที่มีชื่อเดียวกันเข้า scope เดียวกัน ต้องใช้ module พ่อ

อย่างที่คุณเห็น การใช้ module พ่อแยก type Result สองตัว ถ้าแทนเราระบุ use std::fmt::Result และ use std::io::Result เราจะมี type Result สองตัวใน scope เดียว และ Rust จะไม่รู้ว่าเราหมายถึงตัวไหนเมื่อเราใช้ Result

ให้ชื่อใหม่ด้วย Keyword as

มีอีกคำตอบสำหรับปัญหาของการนำสอง type ที่ชื่อเดียวกันเข้า scope เดียวกัน ด้วย use — หลัง path เราระบุ as และชื่อ local ใหม่ หรือ alias ของ type ได้ Listing 7-16 แสดงอีกวิธีในการเขียนโค้ดใน Listing 7-15 โดย เปลี่ยนชื่อหนึ่งใน type Result สองตัวด้วย as

Filename: src/lib.rs
use std::fmt::Result;
use std::io::Result as IoResult;

fn function1() -> Result {
    // --snip--
    Ok(())
}

fn function2() -> IoResult<()> {
    // --snip--
    Ok(())
}
Listing 7-16: เปลี่ยนชื่อ type เมื่อนำเข้า scope ด้วย keyword as

ใน statement use ที่สอง เราเลือกชื่อใหม่ IoResult สำหรับ type std::io::Result ซึ่งจะไม่ขัดกับ Result จาก std::fmt ที่เราก็นำเข้า scope ด้วย Listing 7-15 และ Listing 7-16 ถือเป็น idiomatic ดังนั้นการ เลือกอยู่ที่คุณ!

Re-export ชื่อด้วย pub use

เมื่อเรานำชื่อเข้า scope ด้วย keyword use ชื่อเป็น private ต่อ scope ที่เรา import เข้า ในการเปิดทางให้โค้ดนอก scope นั้นอ้างถึงชื่อนั้น ราวกับว่ามันประกาศใน scope นั้น เรารวม pub และ use ได้ เทคนิคนี้ เรียกว่า re-export เพราะเรากำลังนำ item เข้า scope แต่ก็ทำให้ item นั้น มีให้คนอื่นนำเข้า scope ของพวกเขาด้วย

Listing 7-17 แสดงโค้ดใน Listing 7-11 ที่เปลี่ยน use ใน root module เป็น pub use

Filename: src/lib.rs
mod front_of_house {
    pub mod hosting {
        pub fn add_to_waitlist() {}
    }
}

pub use crate::front_of_house::hosting;

pub fn eat_at_restaurant() {
    hosting::add_to_waitlist();
}
Listing 7-17: ทำให้ชื่อมีให้โค้ดใดก็ใช้จาก scope ใหม่ด้วย pub use

ก่อนการเปลี่ยนนี้ โค้ดภายนอกจะต้องเรียกฟังก์ชัน add_to_waitlist โดยใช้ path restaurant::front_of_house::hosting::add_to_waitlist() ซึ่งจะ ต้องการให้ module front_of_house ถูก mark เป็น pub ด้วย ตอนนี้ pub use นี้ได้ re-export module hosting จาก root module โค้ดภายนอกใช้ path restaurant::hosting::add_to_waitlist() แทนได้

Re-export มีประโยชน์เมื่อโครงสร้างภายในของโค้ดของคุณต่างจากที่โปรแกรมเมอร์ ที่เรียกโค้ดของคุณจะคิดเรื่อง domain เช่น ในการเปรียบเทียบร้านอาหารนี้ คนที่ดำเนินร้านอาหารคิดเรื่อง “front of house” และ “back of house” แต่ ลูกค้าที่เยี่ยมร้านอาหารคงไม่คิดเรื่องส่วนของร้านอาหารในคำเหล่านั้น ด้วย pub use เราเขียนโค้ดของเราด้วยโครงสร้างหนึ่ง แต่เปิดเผยโครงสร้างต่าง ได้ การทำอย่างนั้นทำให้ library ของเราจัดระเบียบดีสำหรับโปรแกรมเมอร์ที่ ทำงานบน library และโปรแกรมเมอร์ที่เรียก library เราจะดูตัวอย่างอีกของ pub use และวิธีที่มันส่งผลต่อ documentation ของ crate ของคุณใน “Export Public API ที่สะดวก” ในบทที่ 14

ใช้ Package ภายนอก

ในบทที่ 2 เราเขียนโปรเจกต์เกมทายตัวเลขที่ใช้ external package ชื่อ rand เพื่อรับตัวเลขสุ่ม ในการใช้ rand ในโปรเจกต์ของเรา เราเพิ่มบรรทัดนี้ใน Cargo.toml:

Filename: Cargo.toml
rand = "0.8.5"

การเพิ่ม rand เป็น dependency ใน Cargo.toml บอก Cargo ให้ download package rand และ dependency ใด ๆ จาก crates.io และทำให้ rand มีให้โปรเจกต์ของเรา

จากนั้น ในการนำ definition ของ rand เข้า scope ของ package ของเรา เรา เพิ่มบรรทัด use เริ่มด้วยชื่อ crate rand และ list item ที่เราอยากนำ เข้า scope จำได้ว่าใน “Generate ตัวเลขสุ่ม” ในบทที่ 2 เรานำ trait Rng เข้า scope และเรียกฟังก์ชัน rand::thread_rng:

use std::io;

use rand::Rng;

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");
}

สมาชิกของ Rust community ทำ package หลายตัวให้ที่ crates.io และการดึงพวกมันใด ๆ เข้า package ของคุณ เกี่ยวข้องกับขั้นตอนเดียวกัน — list พวกมันในไฟล์ Cargo.toml ของ package ของคุณ และใช้ use นำ item จาก crate ของพวกมันเข้า scope

หมายเหตุว่า library std มาตรฐานก็เป็น crate ที่ external ต่อ package ของเรา เพราะ standard library มากับภาษา Rust เราไม่ต้องเปลี่ยน Cargo.toml เพื่อรวม std แต่เราต้องอ้างถึงมันด้วย use เพื่อนำ item จากตรงนั้นเข้า scope ของ package เรา เช่น กับ HashMap เราจะใช้บรรทัด นี้:

#![allow(unused)]
fn main() {
use std::collections::HashMap;
}

นี่เป็น absolute path ที่เริ่มด้วย std ชื่อของ standard library crate

ใช้ Nested Path เพื่อทำความสะอาด List use

ถ้าเราใช้ item หลายตัวที่ประกาศใน crate หรือ module เดียวกัน การ list แต่ละ item บนบรรทัดของตัวเองอาจกินที่แนวตั้งเยอะในไฟล์ของเรา เช่น statement use สองตัวที่เรามีในเกมทายตัวเลขใน Listing 2-4 นำ item จาก std เข้า scope:

Filename: src/main.rs
use rand::Rng;
// --snip--
use std::cmp::Ordering;
use std::io;
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}

แทน เราใช้ nested path นำ item เดียวกันเข้า scope ในบรรทัดเดียวได้ เราทำ สิ่งนี้โดยระบุส่วนที่เหมือนกันของ path ตามด้วย colon สองตัว แล้ว curly bracket รอบ list ของส่วนของ path ที่ต่างกัน ดังที่แสดงใน Listing 7-18

Filename: src/main.rs
use rand::Rng;
// --snip--
use std::{cmp::Ordering, io};
// --snip--

fn main() {
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is: {secret_number}");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    println!("You guessed: {guess}");

    match guess.cmp(&secret_number) {
        Ordering::Less => println!("Too small!"),
        Ordering::Greater => println!("Too big!"),
        Ordering::Equal => println!("You win!"),
    }
}
Listing 7-18: ระบุ nested path เพื่อนำ item หลายตัวที่มี prefix เดียวกันเข้า scope

ในโปรแกรมใหญ่ขึ้น การนำ item หลายตัวเข้า scope จาก crate หรือ module เดียวกันโดยใช้ nested path ลดจำนวน statement use แยกที่ต้องการได้เยอะ!

เราใช้ nested path ในระดับใดของ path ได้ ซึ่งมีประโยชน์เมื่อรวม statement use สองตัวที่แชร์ subpath เช่น Listing 7-19 แสดง statement use สอง ตัว — ตัวหนึ่งที่นำ std::io เข้า scope และตัวหนึ่งที่นำ std::io::Write เข้า scope

Filename: src/lib.rs
use std::io;
use std::io::Write;
Listing 7-19: statement use สองตัวที่ตัวหนึ่งเป็น subpath ของอีกตัว

ส่วนที่เหมือนกันของสอง path นี้คือ std::io และนั่นคือ path แรกสมบูรณ์ ในการรวมสอง path นี้เป็น statement use เดียว เราใช้ self ใน nested path ได้ ดังที่แสดงใน Listing 7-20

Filename: src/lib.rs
use std::io::{self, Write};
Listing 7-20: รวม path ใน Listing 7-19 เป็น statement use เดียว

บรรทัดนี้นำ std::io และ std::io::Write เข้า scope

Import Item ด้วย Glob Operator

ถ้าเราอยากนำ public item ทั้งหมด ที่ประกาศใน path เข้า scope เราระบุ path นั้นตามด้วย glob operator *:

#![allow(unused)]
fn main() {
use std::collections::*;
}

statement use นี้นำ public item ทั้งหมดที่ประกาศใน std::collections เข้า scope ปัจจุบัน ระวังเมื่อใช้ glob operator! Glob ทำให้ยากขึ้นที่จะ บอกว่าชื่อไหนอยู่ใน scope และที่ที่ชื่อที่ใช้ในโปรแกรมของคุณถูกประกาศ นอกจากนี้ ถ้า dependency เปลี่ยน definition สิ่งที่คุณ import เปลี่ยน ด้วย ซึ่งอาจนำไปสู่ compiler error เมื่อคุณ upgrade dependency ถ้า dependency เพิ่ม definition ที่มีชื่อเหมือน definition ของคุณใน scope เดียวกัน เช่น

glob operator มักใช้เมื่อทำการทดสอบ เพื่อนำทุกอย่างภายใต้การทดสอบเข้า module tests เราจะพูดถึงเรื่องนั้นใน “วิธีเขียนเทส” ในบทที่ 11 glob operator บางครั้งก็ใช้เป็นส่วนหนึ่งของ prelude pattern ดู documentation ของ standard library สำหรับข้อมูลเพิ่มเติมเรื่อง pattern นั้น