Đường dẫn để Tham chiếu đến một Item trong Cây Module
Để chỉ cho Rust vị trí một item trong cây module, chúng ta sử dụng một đường dẫn tương tự như cách chúng ta sử dụng đường dẫn khi điều hướng trong hệ thống tệp. Để 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ó hai hình thức:
- Một đường dẫn tuyệt đối là đường dẫn đầy đủ bắt đầu từ gốc của crate; đối
với mã từ một crate bên ngoài, đường dẫn tuyệt đối bắt đầu bằng tên crate, và
đối với mã từ crate hiện tại, nó bắt đầu bằng từ khóa
crate
. - 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 định danh trong module hiện tại.
Cả đườ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ởi dấu hai chấm kép (::
).
Quay lại Listing 7-1, giả sử chúng ta muốn gọi hàm add_to_waitlist
. Điều này
cũng giống như việc hỏi: đâu là đường dẫn của hàm add_to_waitlist
? Listing 7-3
chứa nội dung của Listing 7-1 nhưng đã loại bỏ một số modules và hàm.
Chúng ta sẽ thể hiện 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 gốc của crate. Các đường dẫn này là
đúng, nhưng vẫn còn một vấn đề khác sẽ ngăn ví dụ này biên dịch như hiện tại.
Chúng ta sẽ giải thích lý do sau.
Hàm eat_at_restaurant
là một phần của API công khai của thư viện crate của
chúng ta, vì vậy chúng ta đánh dấu nó bằng từ khóa pub
. Trong phần "Hiển thị
Đường dẫn với từ khóa pub
", chúng ta sẽ đi sâu hơn về
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();
}
Lần đầu tiên chúng ta gọi hàm add_to_waitlist
trong eat_at_restaurant
, chúng
ta sử dụng đường dẫn tuyệt đối. Hàm add_to_waitlist
được định nghĩa trong cùng
crate với eat_at_restaurant
, điều đó 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 đó, chúng ta bao gồm từng
module liên tiếp cho đến khi chúng ta đến được add_to_waitlist
. Bạn có thể
tưởng tượng một hệ thống tệp với cùng cấu trúc: chúng ta sẽ chỉ định đường dẫn
/front_of_house/hosting/add_to_waitlist
để chạy chương trình
add_to_waitlist
; việc sử dụng tên crate
để bắt đầu từ gốc của crate giống
như việc 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 một đường dẫn tương đối. Đường dẫn bắt đầu với front_of_house
, tên của
module được định nghĩa ở cùng cấp độ của cây module với eat_at_restaurant
. Ở
đây, tương đương với hệ thống tệp sẽ là sử dụng đường dẫn
front_of_house/hosting/add_to_waitlist
. Việc bắt đầu bằng tên module 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 tuyệt đối là một quyết định bạn sẽ đưa
ra dựa trên dự án của mình, và nó phụ thuộc vào việc bạn có nhiều khả năng di
chuyển mã định nghĩa item riêng biệt hay cùng với mã sử dụng item đó. Ví dụ, nếu
chúng ta di chuyển module front_of_house
và hàm eat_at_restaurant
vào một
module có tên customer_experience
, chúng ta sẽ cần cập nhật đường dẫn tuyệt
đối đến add_to_waitlist
, nhưng đường dẫn tương đối vẫn sẽ hợp lệ. Tuy nhiên,
nếu chúng ta di chuyển riêng hàm eat_at_restaurant
vào một module có tên
dining
, đường dẫn tuyệt đối đến lệnh gọi add_to_waitlist
sẽ giữ nguyên,
nhưng đường dẫn tương đối sẽ cần được cập nhật. Ưu tiên chung của chúng ta là
chỉ định đường dẫn tuyệt đối vì có khả năng cao hơn là chúng ta sẽ muốn di
chuyển các định nghĩa mã và lệnh gọi các item độc lập với nhau.
Hãy thử biên dịch Listing 7-3 và tìm hiểu tại sao nó chưa thể biên dịch! Các lỗi mà chúng ta nhận được đượ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();
| ^^^^^^^ --------------- 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
Các thông báo lỗi nói rằng module hosting
là riêng tư. Nói cách khác, chúng ta
có các đường dẫn đúng cho module hosting
và hàm add_to_waitlist
, nhưng Rust
không cho phép chúng ta sử dụng chúng vì nó không có quyền truy cập vào các phần
riêng tư. Trong Rust, tất cả các item (hàm, phương thức, cấu trúc, enums,
modules, và hằng số) mặc định là riêng tư đối với các module cha. Nếu bạn muốn
làm cho một item như một hàm hoặc cấu trúc riêng tư, bạn đặt nó trong một
module.
Các item trong một module cha không thể sử dụng các item riêng tư bên trong các module con, nhưng các item trong các module con có thể sử dụng các item trong module tổ tiên của chúng. Điều này là vì các module con bọc và ẩn chi tiết triển khai của chúng, nhưng các module con có thể thấy bối cảnh mà chúng được định nghĩa. Để tiếp tục với ẩn dụ của chúng ta, hãy nghĩ về các quy tắc quyền riêng tư giống như văn phòng phía sau của một nhà hàng: những gì diễn ra ở đó là riêng tư đối với khách hàng nhà hàng, nhưng các 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 để việc ẩ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 mã
bên trong mà bạn có thể thay đổi mà không làm hỏng mã bên ngoài. Tuy nhiên, Rust
có cung cấp cho bạn tùy chọn để hiển thị các phần bên trong của mã module con
cho các module tổ tiên bên ngoài bằng cách sử dụng từ khóa pub
để làm một item
công khai.
Hiển thị Đường dẫn với từ khóa pub
Hãy quay lại lỗi trong Listing 7-4 đã nói với chúng ta rằng module hosting
là
riêng tư. Chúng ta muốn hàm eat_at_restaurant
trong module cha có quyền truy
cập vào hàm add_to_waitlist
trong module con, vì vậy chúng ta đánh dấu module
hosting
bằng từ khóa pub
, như được hiển thị trong Listing 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();
}
Thật không may, mã trong Listing 7-5 vẫn dẫn đến lỗi trình biên dịch, 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: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
Chuyện gì đã xảy ra? Thêm từ khóa pub
trước mod hosting
làm cho module công
khai. Với 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 là riêng tư; việc
làm cho module công khai không làm cho nội dung của nó công khai. Từ khóa pub
trên một module chỉ cho phép mã trong các module tổ tiên của nó tham chiếu đến
nó, không phải truy cập vào mã bên trong của nó. Bởi vì các module là vùng chứa,
không có nhiều điều chúng ta có thể làm chỉ bằng cách làm cho module công khai;
chúng ta cần đi xa hơn và chọn làm cho một hoặc nhiều item trong module công
khai.
Các lỗi trong Listing 7-6 nói rằng hàm add_to_waitlist
là riêng tư. Các quy
tắc quyền riêng tư áp dụng cho các cấu trúc, enums, hàm, và phương thức cũng như
các module.
Hãy cũng làm cho hàm add_to_waitlist
công khai bằng cách thêm từ khóa pub
trước định nghĩa của nó, như trong Listing 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();
}
Bây giờ mã sẽ biên dịch! Để hiểu tại sao việc thêm từ khóa pub
cho phép chúng
ta sử dụng các đường dẫn này trong eat_at_restaurant
liên quan đến các quy tắc
quyền riêng tư, hãy xem đường dẫn tuyệt đối và tương đối.
Trong đường dẫn tuyệt đối, chúng ta bắt đầu với crate
, gốc của cây module của
crate. Module front_of_house
được định nghĩa trong gốc của crate. Mặc dù
front_of_house
không công khai, vì hàm eat_at_restaurant
được định nghĩa
trong cùng module với front_of_house
(nghĩa là, eat_at_restaurant
và
front_of_house
là anh chị em), chúng ta có thể tham chiếu đến front_of_house
từ eat_at_restaurant
. Tiếp theo là module hosting
được đánh dấu là pub
.
Chúng ta có thể truy cập module cha của hosting
, nên chúng ta có thể truy cập
hosting
. Cuối cùng, hàm add_to_waitlist
được đánh dấu là pub
và chúng ta
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 với đường dẫn tuyệt đối ngoại trừ bước
đầu tiên: thay vì bắt đầu từ gốc của crate, đường dẫn bắt đầu từ
front_of_house
. Module front_of_house
được định nghĩa trong cùng module với
eat_at_restaurant
, nên đường dẫn tương đối bắt đầu từ module mà
eat_at_restaurant
được định nghĩa hoạt động. Sau đó, vì hosting
và
add_to_waitlist
được đánh dấu là 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 có kế hoạch chia sẻ crate thư viện của mình để các dự án khác có thể sử dụng mã của bạn, API công khai của bạn là hợp đồng của bạn với người dùng của crate xác định cách họ có thể 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ý thay đổi đối với API công khai của bạn để giúp mọi người dễ dàng hơn khi phụ thuộc vào crate của bạ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 Các Hướng dẫn API của Rust.
Các Phương pháp Tốt nhất cho Packages với Binary và Library
Chúng ta đã đề cập rằng một package có thể chứa cả gốc của binary crate src/main.rs cũng như gốc của library crate src/lib.rs, và cả hai crates sẽ có tên package theo mặc định. Thông thường, các package với mô hình này chứa cả library và binary crate sẽ có đủ lượng mã trong binary crate để khởi động một chương trình thực thi gọi mã được khai báo trong library crate. Điều này cho phép các dự án khác hưởng lợi từ hầu hết chức năng mà package cung cấp vì mã 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ỳ item công khai nào có thể được sử dụng trong binary crate bằng cách bắt đầu đườ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 hoàn toàn sẽ sử dụng library crate: nó chỉ có thể sử dụng API công khai. Điều này giúp bạn thiết kế một API tốt; không chỉ là bạn là tác giả, bạn cũng là khách hàng!
Trong Chương 12, chúng ta sẽ minh họa phương pháp tổ chức này với một chương trình dòng lệnh sẽ chứa cả binary crate và library crate.
Bắt đầu Đường dẫn Tương đối với super
Chúng ta có thể xây dựng đường dẫn tương đối bắt đầu ở module cha, thay vì
module hiện tại hoặc gốc của crate, bằng cách sử dụng super
ở đầu đường dẫn.
Điều này giống như việc bắt đầu một đường dẫn hệ thống tệp với cú pháp ..
có
nghĩa là đi đến thư mục cha. Sử dụng super
cho phép chúng ta tham chiếu đến
một item mà chúng ta biết là trong module cha, điều này có thể làm cho việc sắp
xếp lại cây module dễ dàng hơn khi module có liên quan chặt chẽ với module cha
nhưng module cha có thể được di chuyển đến nơi khác trong cây module vào một
ngày nào đó.
Hãy xem xét mã trong Listing 7-8 mô hình hóa tình huống mà một đầu bếp sửa một
đơn hàng không chính xác và tự mang ra cho khách hàng. Hàm 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
.
fn deliver_order() {}
mod back_of_house {
fn fix_incorrect_order() {
cook_order();
super::deliver_order();
}
fn cook_order() {}
}
Hàm fix_incorrect_order
nằm trong module back_of_house
, nên chúng ta có thể
sử dụng super
để đi đến module cha của back_of_house
, trong trường hợp này
là crate
, gốc. Từ đó, chúng ta tìm kiếm deliver_order
và tìm thấy nó. Thành
công! Chúng ta nghĩ rằng module back_of_house
và hàm deliver_order
có khả
năng sẽ giữ nguyên mối quan hệ với nhau và được di chuyển cùng nhau nếu chúng ta
quyết định tổ chức lại cây module của crate. Do đó, chúng ta đã sử dụng super
để chúng ta sẽ có ít nơi hơn để cập nhật mã trong tương lai nếu mã này được di
chuyển đến một module khác.
Làm cho Structs và Enums Công khai
Chúng ta cũng có thể sử dụng pub
để chỉ định structs và enums là công khai,
nhưng có một vài chi tiết bổ sung đối với việc sử dụng pub
với structs và
enums. Nếu chúng ta sử dụng pub
trước định nghĩa struct, chúng ta làm cho
struct công khai, nhưng các trường của struct vẫn sẽ là riêng tư. Chúng ta có
thể làm cho từng trường công khai hoặc không tùy theo từng trường hợp. Trong
Listing 7-9, chúng ta đã định nghĩa một struct back_of_house::Breakfast
công
khai với một trường toast
công khai nhưng một trường seasonal_fruit
riêng
tư. Điều này mô hình hóa trường hợp trong một nhà hàng mà 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 đi kèm
với bữa ăn dựa trên mùa nào và có sẵn trong kho. Loại 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ể
thấy họ sẽ nhận được loại trái cây nào.
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");
}
Bởi vì trường toast
trong struct back_of_house::Breakfast
là công khai,
trong eat_at_restaurant
chúng ta có thể viết và đọc trường toast
bằng cách
sử dụng ký hiệu dấu chấm. Lưu ý rằng chúng ta không thể sử dụng trường
seasonal_fruit
trong eat_at_restaurant
, bởi vì seasonal_fruit
là riêng tư.
Hãy thử bỏ comment dòng sửa đổi giá trị trường seasonal_fruit
để xem lỗi bạn
nhận được!
Cũng lưu ý rằng bởi vì back_of_house::Breakfast
có một trường riêng tư, struct
cần cung cấp một hàm liên kết công khai để xây dựng một thể hiện của Breakfast
(chúng ta đã đặt tên nó là summer
ở đây). Nếu Breakfast
không có một hàm như
vậy, chúng ta không thể tạo một thể hiện của Breakfast
trong
eat_at_restaurant
bởi vì chúng ta không thể đặt giá trị của trường riêng tư
seasonal_fruit
trong eat_at_restaurant
.
Ngược lại, nếu chúng ta làm cho một enum công khai, tất cả các biến thể của nó
đều công khai. Chúng ta chỉ cần từ khóa pub
trước từ khóa enum
, như được
hiển thị trong Listing 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;
}
Vì chúng ta đã làm cho enum Appetizer
công khai, chúng ta có thể sử dụng các
biến thể Soup
và Salad
trong eat_at_restaurant
.
Enums không hữu ích lắm trừ khi các biến thể của chúng là công khai; sẽ rất
phiền toái khi phải chú thích tất cả các biến thể enum với pub
trong mọi
trường hợp, vì vậy mặc định cho các biến thể enum là công khai. Structs thường
hữu ích mà không cần các trường của chúng là công khai, vì vậy các trường struct
tuân theo quy tắc chung là mọi thứ mặc định là riêng tư trừ khi được chú thích
với pub
.
Có một tình huống khác liên quan đến pub
mà chúng ta chưa đề cập, và đó là
tính năng hệ thống module cuối cùng của chúng ta: từ khóa use
. Chúng ta sẽ đề
cập đến use
riêng lẻ trước, và sau đó chúng ta sẽ thể hiện cách kết hợp pub
và use
.