Đường dẫn tham chiếu đến một mục trong cây module

Để chỉ cho Rust nơi tìm một mục trong cây module, chúng tôi sử dụng một đường dẫn giống như cách chúng ta sử dụng một đường dẫn khi điều hướng một hệ thống tệp. Nếu chúng ta muốn gọi một hàm, chúng ta cần biết đường dẫn của nó.

Một đường dẫn có thể có 2 dạng:

  • Một đường dẫn tuyệt đối bắt đầu từ một crate bằng cách sử dụng tên một crate (đối với mã từ external crate) hoặc theo nghĩa đen crate (cho code từ crate hiện tại).
  • Một đường dẫn tương đối bắt đầu từ module hiện tại và sử dụng self, super, hoặc một mã định danh trong module hiện tại.

Cả hai đường dẫn tuyệt đối và tương đối đều được theo sau bởi một hoặc nhiều định danh được phân tách bằng dấu hai chấm kép(::).

Hãy quay lại ví dụ trong Listing 7-1. Làm thế nào để chúng tôi gọi hàm add_to_waitlist? Điều này cũng giống như yêu cầu, đừng dẫn của function là gì add_to_waitlist ? Listing 7-3 chứa Listing 7-1 với một số module và hàm bị loại bỏ. Chúng tôi sẽ chỉ ra hai cách để gọi hàm add_to_waitlist từ một hàm mới eat_at_restaurant được định nghĩa trong crate root. Hàm eat_at_restaurant là một phần của public API trong thư viện crate của chúng ta , vì vậy chúng ta đánh dấu nó với từ khóa pub. Trong phần “Exposing Paths with the pub Keyword”, Chúng ta sẽ đi vào chi tiết hơn về pub.Lưu ý rằng ví dụ này sẽ chưa biên dịch; chúng tôi sẽ giải thích một chút lý do tại sao.

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: Gọi hàm add_to_waitlistsử dụng các đường dẫn tuyệt đối và tương đối

Lần đầu tiên chúng ta gọi hàm add_to_waitlist trong eat_at_restaurant, chúng tôi sử dụng một đường dẫn tuyệt đối. Hàm add_to_waitlistđược xác định trong cùng một crate như eat_at_restaurant, có nghĩa là chúng ta có thể sử dụng từ khóa crate để bắt đầu một đường dẫn tuyệt đối.

Sau crate, chúng tôi bao gồm từng module kế tiếp nhau cho đến khi chúng ta thực hiện theo cách add_to_waitlist.Bạn có thể tưởng tượng một hệ thống tệp có cấu trúc giống nhau, và chúng tôi sẽ chỉ định đường dẫn /front_of_house/hosting/add_to_waitlist để chạy chương trình add_to_waitlist ; sử dụng tên crate để bắt đầu từ crate root giống như sử dụng / để bắt đầu từ gốc hệ thống tệp trong shell của bạn.

Lần thứ hai chúng ta gọi add_to_waitlist trong eat_at_restaurant, chúng ta sử dụng đường dẫn tương đối. Đường dẫn bắt đầu với front_of_house, tên của module được xác định ở cùng cấp của cây module như eat_at_restaurant. Ở đây hệ thống tệp tương đương sẽ sử dụng đường dẫn front_of_house/hosting/add_to_waitlist. Bắt đầu bằng một cái tên có nghĩa là đường dẫn đó là tương đối.

Việc chọn sử dụng đường dẫn tương đối hay đường dẫn tuyệt đối là quyết định bạn sẽ thực hiện dựa trên dự án của mình. Quyết định sẽ phụ thuộc vào việc bạn có nhiều khả năng di chuyển các mục code được xác định riêng biệt hoặc cùng với code sử dụng các mục hay không. Ví dụ, nếu chúng ta di chuyển module front_of_house và hàmeat_at_restaurant vào một module được đặt tên customer_experience, chúng tôi cần cập nhật đường dẫn tuyệt đối đếnadd_to_waitlist, nhưng đường dẫn tương đối sẽ vẫn hợp lệ. Tuy nhiên, nếu chúng ta di chuyển hàm eat_at_restaurant riêng biệt thành một module có tên dining,đường dẫn tuyệt đối dẫn đến add_to_waitlist cuộc gọi sẽ giữ nguyên, nhưng đường dẫn tương đối sẽ cần được cập nhật. Sở thích của chúng tôi là chỉ định đường dẫn tuyệt đối vì có nhiều khả năng chúng tôi muốn di chuyển các định nghĩa code và lệnh gọi các mục độc lập với nhau.

Hãy thử biên dịch Listing 7-3 và tìm hiểu lý do tại sao nó vẫn chưa được biên dịch! Lỗi chúng tôi gặp phải được hiển thị trong 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();
  |                            ^^^^^^^ 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();
   |                     ^^^^^^^ 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` due to 2 previous errors

Listing 7-4: Lỗi trình biên dịch khi xây dựng code trong Listing 7-3

Các thông báo lỗi cho biết module hosting là private. Nói cách khác, chúng tôi có các đường dẫn chính xác cho module hosting và hàm add_to_waitlist,nhưng Rust sẽ không cho phép chúng tôi sử dụng chúng vì nó không có quyền truy cập vào các phần private.

Các module không chỉ hữu ích cho việc tổ chức code của bạn. Chúng cũng định nghĩa Rust’s privacy boundary: bao gồm chi tiết triển khai code bên ngoài không được phép biết, gọi hoặc phụ thuộc vào. Vì vậy, nếu bạn muốn đặt một mục như function hoặc structs ở chế độ private, bạn đặt nó trong một module.

Cách thức hoạt động của quyền riêng tư trong Rust là tất cả các mục (functions, methods, structs, enums, modules, and constants) là private theo mặc định. Các mục ở module cha không thể sử dụng các mục private bên trong các module con,nhưng các mục trong module con có thể sử dụng các mục trong module tổ tiên của chúng. Lý do là các module con bao bọc và ẩn triển khai chi tiết của chúng, nhưng các module con có thể thấy ngữ cảnh mà chúng được xác định. Để tiếp tục với phép ẩn dụ về nhà hàng, hãy nghĩ về các quy tắc bảo mật giống như văn phòng phía sau của một nhà hàng: những gì diễn ra trong đó là riêng tư đối với khách hàng của nhà hàng, nhưng những người quản lý văn phòng có thể thấy và làm mọi thứ trong nhà hàng mà họ điều hành.

Rust đã chọn để hệ thống module hoạt động theo cách này để ẩn các chi tiết triển khai bên trong là mặc định. Bằng cách đó, bạn biết những phần nào của code bên trong mà bạn có thể thay đổi mà không cần phá vỡ mã bên ngoài. Nhưng bạn có thể hiển thị các phần bên trong của code module con cho các module tổ tiên bên ngoài bằng cách sử dụng từ khóa pub để đặt một mục ở chế độ public.

Hiển thị các Đường dẫn với Từ khoá pub

Hãy quay lại lỗi trong Listing 7-4 Hãy quay lại lỗi trong module hosting là private.Chúng ta muốn function eat_at_restaurant trong module cha để có quyền truy cập vào functionadd_to_waitlist trong module con, vì vậy chúng ta đánh dấu module hosting với từ khóa pub, như được hiển thị trong Listing 7-5.

Filename: src/lib.rs

mod front_of_house {
    pub 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-5: Khai báo module hosting như pub sử dụng nó từ eat_at_restaurant

Thật không may, mã trong Listing 7-5 vẫn dẫn đến lỗi, như được hiển thị trong Listing 7-6.

$ cargo build
   Compiling restaurant v0.1.0 (file:///projects/restaurant)
error[E0603]: function `add_to_waitlist` is private
 --> src/lib.rs:9:37
  |
9 |     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:12:30
   |
12 |     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` due to 2 previous errors

Listing 7-6:Lỗi trình biên dịch khi xây dựng mã trong Listing 7-5

Chuyện gì đã xảy ra? Thêm từ khóa pub vào trước mod hosting đặt module ở chế độ public. Với sự thay đổi này, nếu chúng ta có thể truy cập front_of_house, chúng ta có thể truy cập hosting. Nhưng nội dung của hosting vẫn còn private; đặt module ở chế độ public không làm public nội dung của module. Từ khóa pub trên một module chỉ cho phép code trong các module tổ tiên của nó tham chiếu đến nó.

Các lỗi trong Listing 7-6 nói rằng function add_to_waitlist là private. Các quy tắc bảo mật áp dụng cho structs, enums, functions, and methods cũng như các module.

Hãy cũng làm cho function add_to_waitlist public bằng cách thêm từ khóa pub trước định nghĩa của nó, như trong Listing 7-7.

Filename: src/lib.rs

mod front_of_house {
    pub mod hosting {
        pub 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-7: Thêm từ khóa pub vào mod hostingfn add_to_waitlist cho phép chúng tôi gọi hàm từ eat_at_restaurant

Bây giờ code sẽ biên dịch!Hãy xem xét đường dẫn tuyệt đối và đường dẫn tương đối và kiểm tra kỹ lý do tại sao lại thêm từ khóa pub cho phép chúng tôi sử dụng những đường dẫn này trong add_to_waitlist đối với các quy tắc bảo mật.

Trong đường dẫn tuyệt đối, chúng ta bắt đầu với crate, root của cây crate’s module . Sau đó module front_of_houseđược định nghĩa trong crate root. module front_of_house không là public, nhưng bởi vì function eat_at_restaurant được định nghĩa trong cùng một module như front_of_house (đó là, eat_at_restaurantfront_of_house là chị em), chúng ta có thể tham khảo front_of_house từ eat_at_restaurant.Tiếp theo là module hosting được đánh dấu với pub. Chúng ta có thể truy cập module cha của hosting, vì vậy chúng ta có thể truy cập hosting.Cuối cùng function add_to_waitlist được đánh dấu với pub và chúng tôi có thể truy cập module cha của nó, vì vậy lệnh gọi hàm này hoạt động!

Trong đường dẫn tương đối, logic giống như đường dẫn tuyệt đối ngoại trừ bước đầu tiên: thay vì bắt đầu từ crate root, đường dẫn bắt đầu từ front_of_house. Module front_of_house được định nghĩa trong cùng một module như eat_at_restaurant, vì vậy đường dẫn tương đối bắt đầu từ module trong đó eat_at_restaurant là việc định nghĩa. Sau đó,bởi vì hostingadd_to_waitlist được đánh dấu với pub, phần còn lại của đường dẫn hoạt động và lệnh gọi hàm này hợp lệ!

Nếu bạn định chia sẻ library crate của mình để các dự án khác có thể sử dụng code của bạn, Public API của bạn là hợp đồng của bạn với người dùng trong crate về cách họ tương tác với mã của bạn. Có nhiều cân nhắc xung quanh việc quản lý các thay đổi đối với public API của bạn để giúp mọi người phụ thuộc vào crate của bạn dễ dàng hơn. Những cân nhắc này nằm ngoài phạm vi của cuốn sách này; nếu bạn quan tâm đến chủ đề này, hãy xem The Rust API Guidelines.

Các phương pháp hay nhất cho các package có một Binary và a Library

Chúng tôi đã đề cập đến một package có thể chứa cả hai src/main.rs binary crate root giống như một src/lib.rs library crate root, và cả crates sẽ có tên package theo mặc định. hông thường, các package có mẫu này sẽ chỉ có đủ code trong binary crate để bắt đầu một tệp thực thi gọi code bằng library crate. Điều này cho phép các project khác được hưởng lợi nhiều nhất chức năng mà package cung cấp, vì code của library crate có thể được chia sẻ.

Cây module nên được định nghĩa trong src/lib.rs. Sau đó, bất kỳ mục public nào có thể được sử dụng trong binary crate bằng cách bắt đầu các đường dẫn với tên của package. Binary crate trở thành người dùng của library crate giống như một crate bên ngoài sẽ sử dụng library crate: nó chỉ có thể sử dụng public API. Điều này giúp bạn thiết kế một API tốt; bạn không chỉ là tác giả, bạn còn là client!

In Chapter 12,chúng ta sẽ chứng minh tổ chức này thực hành với một command-line program sẽ chứa cả một binary crate và một library crate.

Bắt đầu Đường dẫn tương đối với super

Chúng tôi cũng có thể xây dựng các đường dẫn tương đối bắt đầu trong module cha bằng cách sử dụng super ở đầu đường dẫn. Điều này giống như bắt đầu một đường dẫn hệ thống tệp với cú pháp ... Tại sao chúng tôi muốn làm điều này?

Hãy xem xét đoạn mã trong Listing 7-8 mô hình hóa tình huống trong đó đầu bếp sửa một đơn hàng không chính xác và đích thân mang món đó ra cho khách hàng.Chức năng fix_incorrect_order được định nghĩa trong module back_of_house gọi hàm deliver_order được định nghĩa trong module cha bằng cách chỉ định đường dẫn đến deliver_order bắt đầu với 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: Calling a function using a relative path starting with super

Hàm fix_incorrect_order nằm trong module back_of_house, vì vậy chúng ta có thể sử dụng super để chuyển đến module cha của back_of_house, trong trường hợp này là crate root. Từ đó, chúng tôi tìm kiếm delivery_order và tìm thấy nó. Thành công! Chúng tôi nghĩ rằng module back_of_house và hàm delivery_order có thể giữ nguyên mối quan hệ với nhau và được di chuyển cùng nhau nếu chúng tôi quyết định tổ chức lại cây module của crate. Do đó, chúng tôi đã sử dụng super nên chúng tôi sẽ có ít nơi để cập nhật mã hơn trong tương lai nếu mã này được chuyển sang một module khác.

Làm cho Structs và Enums Public

Chúng ta cũng có thể sử dụng pub để chỉ định các struct và enums là public, nhưng có một số chi tiết bổ sung. Nếu chúng ta sử dụng pub trước định nghĩa struct, chúng ta đặt struct ở chế độ public, nhưng các trường của struct sẽ vẫn ở chế độ riêng tư. Chúng tôi có thể public từng trường hoặc không tùy từng trường hợp cụ thể. Trong Listing 7-9, chúng tôi đã xác định cấu trúc back_of_house :: Breakfast public với trường toast public nhưng là trường season_fruit riêng. Điều này mô tả trường hợp trong một nhà hàng, nơi khách hàng có thể chọn loại bánh mì đi kèm với bữa ăn, nhưng đầu bếp quyết định loại trái cây nào đi kèm với bữa ăn dựa trên những gì đang có trong mùa và trong kho. Trái cây có sẵn thay đổi nhanh chóng, vì vậy khách hàng không thể chọn trái cây hoặc thậm chí không thể xem họ sẽ lấy trái cây nào.

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:Một struct với một số trường public và một số trường riêng tư

Vì trường toast trong struct back_of_house :: Breakfast là public nên trong eat_at_restaurant, chúng ta có thể viết và đọc trường toast bằng ký hiệu :: . Xin lưu ý rằng chúng ta không thể sử dụng trường season_fruit trong eat_at_restaurant bởi vì season_fruit là private. Hãy thử bỏ ghi chú dòng sửa đổi giá trị trường season_fruit để xem bạn gặp lỗi gì!

Ngoài ra, hãy lưu ý rằng vì back_of_house :: Breakfast có một trường private, nên struct cần cung cấp một hàm liên kết public để tạo một phiên bản của Breakfast (chúng tôi đã đặt tên nó là summer ở đây). Nếu Breakfast không có chức năng như vậy, chúng tôi không thể tạo phiên bản của Breakfast trong eat_at_restaurant vì chúng tôi không thể đặt giá trị của trường private theo seasonal_fruit trong eat_at_restaurant.

Ngược lại, nếu chúng ta public enum, thì tất cả các biến thể của nó sẽ được public. Chúng tôi chỉ cần pub trước từ khóa enum, như được hiển thị trong 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:Việc chỉ định một enum là public làm cho tất cả các biến thể của nó ở chế độ public

Vì chúng tôi đã đặt enum Appetizer ở chế độ public nên chúng tôi có thể sử dụng các biến thể SoupSalad trong eat_at_restaurant. Enums không hữu ích lắm trừ khi các biến thể của chúng được public; sẽ rất khó chịu khi phải chú thích tất cả các biến thể enum bằng pub trong mọi trường hợp, vì vậy mặc định cho các biến thể enum là public. Các struct thường hữu ích mà không cần trường của chúng là public, vì vậy, các trường của struct tuân theo quy tắc chung là mọi thứ là private theo mặc định trừ khi được chú thích bằng pub.

Còn một trường hợp nữa liên quan đến pub mà chúng tôi chưa đề cập đến và đó là tính năng hệ thống public cuối cùng của chúng tôi: từ khóa use. Trước tiên, chúng tôi sẽ đề cập đến vấn đề use, sau đó chúng tôi sẽ chỉ ra cách kết hợp giữa pubuse.