Danh sách bài viết

Bài 115: self / super / crate — Module Path

Bài 115 của series Rust Cơ Bản — 3 special keyword điều hướng cây module trong Rust: self (module hiện tại, tương ứng ./), super (parent module, tương ứng ../), crate (root crate, tương ứng /). Bài này so sánh absolute path (crate::) với relative path (super::/self::), khi nào dùng cái nào trong cross-module xa hay sibling cùng folder, và convention mà Rust API Guidelines khuyến nghị cho project lớn.

09/06/2026
10 phút đọc
2 lượt xem
1

Mục Tiêu Bài Học

Sau bài học, bạn sẽ:

  • Biết 3 special path keyword Rust: self, super, crate — và ánh xạ với ./, ../, / trong filesystem để dễ nhớ.
  • Viết được use crate::routes::users; dạng absolute bắt đầu từ root crate, cho cross-module import đường dài.
  • Viết được use super::config; dạng relative đi lên parent module, để import sibling trong cùng folder.
  • Hiểu self:: phần lớn redundant (default đã là self), nhưng vẫn hữu dụng trong nested use dạng use foo::{self, bar}.
  • Phân biệt khi nào nên dùng absolute (cross-module xa, public API readable) và khi nào nên dùng relative (sibling cùng folder, refactor-friendly khi move cả folder).
  • Áp dụng convention Rust API Guidelines: prefer absolute crate:: cho clarity trong project lớn.
2

3 Special Path Keyword

Mỗi crate Rust có một cây module bắt đầu từ root (file main.rs với binary, hoặc lib.rs với library). Trong cây đó, một item (function, struct, enum, mod) có thể được tham chiếu qua một path — chuỗi tên cách nhau bởi ::. Rust cung cấp ba từ khóa đặc biệt làm điểm xuất phát của path, mỗi cái có ý nghĩa rõ ràng:

  • self — bắt đầu từ module hiện tại. Tương tự ./ trong filesystem: "ở ngay đây".
  • super — bắt đầu từ parent module, tức module chứa module hiện tại. Tương tự ../: "lên một tầng".
  • crate — bắt đầu từ root crate, tức gốc cây module. Tương tự / (absolute path từ root).

Hình dung một cấu trúc 3 tầng đơn giản:

crate (root: src/main.rs)
├── config              (mod config;       → src/config.rs)
├── routes              (mod routes;       → src/routes/mod.rs)
│   ├── users           (mod users;        → src/routes/users.rs)
│   └── posts           (mod posts;        → src/routes/posts.rs)
└── services            (mod services;     → src/services/mod.rs)
    ├── auth            (mod auth;         → src/services/auth.rs)
    └── db              (mod db;           → src/services/db.rs)

Từ bên trong routes::users, bạn có thể tham chiếu:

  • self::helper — item helper nằm trong chính routes::users.
  • super::posts — sibling module posts (cùng parent routes).
  • crate::services::auth — đi xuyên cây từ root xuống services::auth.

Ba keyword này dùng được cả trong use statement (import) lẫn trong path inline (gọi trực tiếp crate::config::PORT). Tất cả ví dụ ở các mục sau đều dựa trên cấu trúc trên.

3

crate:: — Absolute Path Từ Root

crate luôn trỏ về root của crate hiện tại — file main.rs (binary) hoặc lib.rs (library). Mọi path bắt đầu bằng crate:: đều là absolute: nghĩa hoàn toàn không phụ thuộc nơi viết. Dù bạn đang ở routes::users, services::db, hay sâu hơn nữa, crate::config::PORT luôn trỏ tới cùng một item.

// src/main.rs (root crate)
mod config;
mod routes;
mod services;

fn main() {
    println!("port = {}", config::PORT);
}

// src/config.rs
pub const PORT: u16 = 8080;
pub const HOST: &str = "0.0.0.0";

// src/routes/users.rs
use crate::config::PORT;             // absolute: từ root đi xuống config::PORT
use crate::services::db;             // absolute: import sibling-of-parent

pub fn list() {
    println!("listening on port {PORT}");
    db::query("SELECT * FROM users");
}

// src/services/auth.rs
use crate::config::HOST;             // cùng cú pháp absolute, dù file nằm ở folder khác
use crate::routes::users;            // cross-module

pub fn check() {
    println!("auth checking host = {HOST}");
    users::list();
}

Idiom của absolute path: "đọc xong import là biết item nằm đâu trong crate" — không cần mở terminal để xem file đang ở folder nào. Đây là cách tiếp cận chính trong các dự án lớn — Cargo crate phổ biến (tokio, axum, serde) đều dùng crate:: cho hầu hết import cross-module.

4

super:: — Relative Lên 1 Tầng

super trỏ về parent module của module hiện tại — đúng nghĩa lên một tầng. Có thể chain nhiều lần: super::super:: lên hai tầng (hiếm khi đẹp về code, nhưng hợp lệ). Useful nhất khi muốn import sibling: hai file cùng folder, cùng có parent — chỉ cần lên một tầng rồi xuống lại.

// src/routes/mod.rs
pub mod users;
pub mod posts;

// src/routes/users.rs
use super::posts;                    // lên 1 tầng (routes), xuống lại posts (sibling)

pub fn list_with_posts() {
    println!("listing users");
    posts::list();                   // gọi sibling
}

// src/routes/posts.rs
use super::users;                    // ngược lại: posts dùng users

pub fn list() {
    println!("listing posts");
}

// Có thể chain nhiều super:: nhưng hiếm khi đẹp
// src/services/db/postgres.rs
mod connection {
    use super::super::db;            // lên 2 tầng: từ connection → db → services::db
                                     // → tương đương crate::services::db
}

super:: rất hữu dụng trong submodule muốn truy cập định nghĩa chung ở parent. Ví dụ: routes/users.rsroutes/posts.rs là hai sibling — nếu refactor đổi tên folder routes/ thành handlers/, các import super:: giữa chúng không cần sửa. Còn nếu viết crate::routes::posts, mọi nơi đều phải đổi thành crate::handlers::posts.

5

self:: — Cùng Module Hiện Tại

self bắt đầu path từ chính module hiện tại. Phần lớn trường hợp self::thừa — vì default của một path không có prefix đã là self. use self::helper::formatuse helper::format hoàn toàn tương đương khi helper là child module trong cùng file.

// src/routes/users.rs
mod helper {
    pub fn format(name: &str) -> String {
        format!("[user] {name}")
    }
}

// Cả 2 dòng dưới đây tương đương:
use self::helper::format;            // explicit self
use helper::format;                  // implicit (default đã là self)

pub fn greet(name: &str) {
    println!("{}", format(name));
}

Nơi self:: thật sự hữu dụng là trong nested use khi muốn vừa import module vừa import item bên trong nó:

// Nested use - self ở đây có nghĩa "import chính module này"
use std::io::{self, Read, Write};
// ↑ tương đương 3 dòng:
//   use std::io;
//   use std::io::Read;
//   use std::io::Write;

fn read_stdin() -> io::Result<String> {  // dùng io::Result được vì có self
    let mut s = String::new();
    io::stdin().read_to_string(&mut s)?;
    Ok(s)
}

Trong nested form, self là cách duy nhất để biểu thị "module gốc của block {...}" cùng lúc với các con của nó. Không có self ở đây, bạn sẽ phải viết hai dòng use riêng. Đây là use case chính đáng nhất của từ khóa này.

6

Absolute Vs Relative Path

Hai cách tiếp cận có ưu nhược điểm rõ ràng — so sánh trực tiếp:

// Cùng tham chiếu services::auth từ trong routes::users
// ─── Cách 1: absolute (crate::) ───
use crate::services::auth;
//   → Đọc: rõ ràng auth ở "crate root → services → auth"
//   → Move file routes/users.rs sang folder khác: KHÔNG cần sửa
//   → Đổi tên folder services/ thành svc/: PHẢI sửa "crate::svc::auth"

// ─── Cách 2: relative (super::) ───
use super::super::services::auth;
//   → Đọc: phải biết file hiện tại đang ở đâu để hiểu "lên 2 tầng rồi xuống"
//   → Move file routes/users.rs sang folder khác: PHẢI tính lại số super::
//   → Đổi tên folder services/ thành svc/: vẫn phải sửa thành "super::super::svc::auth"

Tổng kết khác biệt:

  • Absolute (crate::): rõ ràng, stable về vị trí; đọc xong biết item đó ở đâu trong crate; không bị ảnh hưởng khi move file (giữ nguyên path).
  • Relative (super::, self::): ngắn hơn khi target gần; refactor folder dễ (move cả folder không phải sửa); nhưng đọc một dòng use super::super:: phải biết file đang ở folder nào mới hiểu đầy đủ.

Không có cái nào "đúng tuyệt đối" — phụ thuộc khoảng cách path và cấu trúc thay đổi của project.

7

Khi Nào Dùng Absolute

Ưu tiên crate:: trong các tình huống:

  • Cross-module call xa: từ routes::api::v1::users muốn dùng services::db::pool. Viết crate::services::db::pool tường minh hơn là super::super::super::services::db::pool — đếm super:: dễ sai.
  • Public API và utility chia sẻ: hằng số global, type công khai trong lib.rs, các helper toàn project — đường tới chúng từ bất cứ đâu đều rõ qua crate::.
  • Readable không cần biết file ở đâu: khi review PR trên GitHub hay đọc snippet rời, use crate::services::auth; tự đủ thông tin; còn use super::auth; phải mở file thư mục mới hiểu.
  • Code generator / macro: macro mở rộng ở chỗ khác file gốc — nếu dùng super:: tương đối, kết quả expand có thể trỏ sai. crate:: tuyệt đối luôn đúng. Đây là lý do hầu hết macro chính thống (Tokio, Serde) chỉ generate ::crate_name:: hoặc crate::.
8

Khi Nào Dùng Relative

Ưu tiên super::/self:: khi:

  • Sibling import trong cùng folder: hai file users.rsposts.rs cùng nằm trong routes/use super::posts; ngắn và rõ "lấy sibling".
  • Sub-module bên trong module ngang hàng: trong services/db/mod.rs tham chiếu services/db/postgres.rs bằng self::postgres hay use postgres; đều ngắn hơn crate::services::db::postgres.
  • Refactor friendly khi move cả folder: nếu chuyển toàn bộ routes/ thành web::routes/, mọi super:: giữa các file con của routes/ vẫn đúng. Còn crate::routes::... ngoài folder phải sửa thành crate::web::routes::... — đổi lại, code bên trong routes/ không phải sửa.
  • Test module trong file: pattern #[cfg(test)] mod tests { use super::*; } rất phổ biến — test cần truy cập tất cả item ở module cha, dùng super::* ngắn gọn. Đây là use case "kinh điển" của super:: trong code Rust.
9

Best Practice

  • Mỗi project chọn 1 convention chính. Cả team viết cùng kiểu, không trộn random — đọc PR sẽ ổn định hơn. Khuyến nghị: cross-module dùng crate::, intra-module/test dùng super::.
  • Rust API Guidelines prefer absolute crate:: cho clarity, đặc biệt với library public — người dùng đọc source crate dễ định vị item.
  • Không dùng self:: redundant. use self::helper khi đã đủ với use helper chỉ thêm noise. Giữ self cho nested form {self, X, Y}.
  • Cảnh giác chain super::super::super::. Quá 2 tầng super:: là dấu hiệu cấu trúc cần refactor — đổi sang crate:: sẽ rõ và an toàn hơn.
  • Format with rustfmt. cargo fmt sẽ sort import theo nhóm (std → extern crate → crate::super::/self::), đồng nhất style cả project.
10

Tổng Kết

  • 3 special path keyword: self (module hiện tại, như ./), super (parent module, như ../), crate (root crate, như /).
  • crate:: = absolute path: ý nghĩa không đổi dù viết ở đâu trong cây module; idiom cho cross-module import xa và macro-generated code.
  • super:: = relative lên 1 tầng: tốt cho sibling import; chain nhiều super:: trên 2 lần là dấu hiệu nên đổi sang crate::.
  • self:: phần lớn redundant vì default đã là self; chỉ thực sự cần trong nested use use foo::{self, bar} để vừa import module vừa import con.
  • Absolute vs relative: absolute = rõ và stable; relative = ngắn và refactor friendly khi move cả folder.
  • Convention: Rust API Guidelines prefer absolute crate:: cho clarity; trường hợp dùng super:: kinh điển là test module use super::*;.
11

Bài Tập Củng Cố

Tự trả lời, đáp án ở cuối:

  1. Cho cây module crate → handlers → api → users. Từ trong users.rs, viết hai cách import module handlers::api::posts: một dùng crate::, một dùng super::. Cái nào ngắn hơn?
  2. Cho file src/services/db.rs có nội dung mod connection { ... }. Bên trong connection, viết use để truy cập crate::config::HOST theo 3 cách: absolute, relative bằng super::, và trộn (lên một tầng rồi crate). Cách nào dễ đọc nhất?
  3. Test module #[cfg(test)] mod tests { ... } nằm trong src/services/auth.rs. Bên trong tests, viết một dòng use để bring toàn bộ item của module cha (auth) vào scope. Tại sao đây là pattern phổ biến nhất của super::?
  4. Bạn viết use self::helper::format; trong một file có child module helper. Đồng nghiệp review nói "bỏ self:: đi". Có nên bỏ không? Tại sao?
  5. So sánh hai dòng: use std::io::{self, Read};use std::io; use std::io::Read;. Chúng tương đương về mặt ngữ nghĩa không? self ở đây có nghĩa gì?
  6. Project có cấu trúc routes/ đang bị refactor đổi tên thành handlers/. File routes/users.rs hiện đang use super::posts;use crate::routes::common;. Sau khi đổi tên folder, dòng nào cần sửa, dòng nào không?
Đáp án
  1. Absolute: use crate::handlers::api::posts;. Relative: use super::posts; (chỉ 1 tầng vì posts là sibling của users trong cùng api). Relative ngắn hơn rõ rệt — đây là use case lý tưởng cho super::.
  2. Absolute: use crate::config::HOST; (luôn đúng, đọc rõ). Relative full: use super::super::config::HOST; (đếm super:: dễ sai). Trộn: không thực sự có "trộn" được — crate chỉ dùng ở đầu path. Absolute dễ đọc nhất; đây là minh họa rõ vì sao API Guidelines prefer crate::.
  3. use super::*;. Đây là pattern phổ biến vì test cần truy cập tất cả private item của module cha (cả pub lẫn private trong cùng crate đều thấy nhau theo Rust visibility rules), và super::* là cách ngắn gọn nhất.
  4. Nên bỏ. self:: ở đây không thêm thông tin gì — default đã là self. Code base nhất quán không có self:: redundant đọc gọn hơn. Chỉ giữ self khi nested form {self, ...}.
  5. Có, hoàn toàn tương đương. self trong nested form nghĩa là "import chính module io" (để dùng io::Result, io::stdin()) cùng lúc với các con (Read). Không có self, chỉ use std::io::Read; sẽ không bring io vào scope.
  6. use super::posts; không cần sửa — vẫn lên 1 tầng (giờ là handlers/) rồi xuống posts. use crate::routes::common; phải sửa thành use crate::handlers::common;. Đây chính là điểm relative path "thắng" khi refactor cả folder.
12

Bài Tiếp Theo

Bài 116: pub use — Re-export — biết path rồi, bước tiếp là phơi item ra ngoài qua một path khác. pub use foo::bar; không chỉ bring bar vào scope, mà còn re-export nó như thành viên của module hiện tại. Đây là kỹ thuật xương sống của facade pattern trong lib.rs — flatten public API, ẩn cấu trúc nội bộ, kiểm soát những gì user thấy.