Danh sách bài viết

Bài 30: Quy Ước Đặt Tên Trong Rust: snake_case, UpperCamelCase, SCREAMING_SNAKE

Bài 30 của series Rust Cơ Bản — bộ quy ước đặt tên chính thức RFC 430: snake_case cho variable / function / module / file / crate identifier, UpperCamelCase cho type / struct / enum / trait / type alias / type parameter, SCREAMING_SNAKE_CASE cho const / static, kebab-case cho package name trong Cargo.toml, quy tắc xử lý acronym kiểu HttpClient, và clippy lint enforce naming tự động.

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

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

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

  • Nắm được bộ quy ước đặt tên chính thức của Rust được chốt trong RFC 430: convention nào áp dụng cho loại identifier nào.
  • Biết khi nào dùng snake_case (variable, function, method, module, file), UpperCamelCase (type, struct, enum, trait, type alias, type parameter), SCREAMING_SNAKE_CASE (const, static).
  • Phân biệt crate name (identifier trong code) với package name trong Cargo.toml — vì sao package cho phép kebab-case nhưng crate identifier thì không.
  • Biết cách Rust xử lý acronym trong tên type — vì sao idiom là HttpClient chứ không phải HTTPClient.
  • Hiểu các clippy lint non_snake_case, non_camel_case_types, non_upper_case_globals tự động cảnh báo khi sai convention, và cách silence hợp lý với #[allow(...)] cho FFI binding.
  • Tổng kết Nhóm 4 (let, mut, shadowing, const, static, naming) và chuẩn bị sang Nhóm 5 — scalar types.
2

Bảng Tổng Quan Convention

RFC 430 chốt convention cho từng loại item. Bảng dưới là bản tổng hợp đầy đủ — các mục tiếp theo trong bài sẽ đào sâu từng nhóm.

Item Convention Ví dụ
Variable, function, method snake_case user_name, fn calculate_total()
Module, source file snake_case mod auth_service, auth_service.rs
Crate (identifier code) snake_case use tokio::sync;
Package (Cargo.toml name) kebab-case OK name = "my-web-app"
Type / Struct / Enum / Trait UpperCamelCase User, HttpStatus, Display
Type parameter generic UpperCamelCase ngắn 1-2 chữ T, K, V, Iter
Const, Static SCREAMING_SNAKE_CASE MAX_RETRIES, APP_NAME
Enum variant UpperCamelCase Ok, Err, None, NotFound
Lifetime lowercase 1 chữ + tick 'a, 'b, 'src
Macro snake_case + ! println!, vec!

Ghi nhớ nhanh: "giá trị runtime" → snake_case (variable, function, module), "khái niệm type-level" → UpperCamelCase (struct, trait, generic param), "constant compile-time / global" → SCREAMING_SNAKE (const, static).

3

snake_case Cho Variable / Function / Module

snake_case là convention cho mọi thứ ở "value level": biến (binding), function, method, module, và source file. Quy tắc: chỉ ký tự ASCII thường, các từ phân tách bằng dấu gạch dưới _.

// Variable: snake_case
let user_name = "An";
let total_amount = 1500;
let is_active = true;

// Function: snake_case
fn calculate_total(items: &[u32]) -> u32 {
    items.iter().sum()
}

fn send_email_to_user(user_id: u64, subject: &str) {
    // ...
}

// Method (trong impl): snake_case
struct User { id: u64 }
impl User {
    fn full_name(&self) -> String { String::new() }
    fn last_login_at(&self) -> u64 { 0 }
}

// Module: snake_case
mod auth_middleware;        // tham chiếu file auth_middleware.rs
mod payment_gateway;        // tham chiếu file payment_gateway.rs

// Macro: snake_case + !
println!("hello");
vec![1, 2, 3];

Vài lưu ý nhỏ:

  • Không dùng camelCase kiểu userName — compiler sẽ warning non_snake_case (mục 9).
  • Không viết tắt khó hiểu: usr_nm tệ, user_name tốt; num_retries chấp nhận được, n_r không.
  • Function trả về bool thường bắt đầu bằng is_, has_, can_: is_empty(), has_permission(), can_edit().
  • Tên file Rust source chính là tên module — auth_middleware.rs tự động trở thành mod auth_middleware khi mod auth_middleware; được khai báo ở file cha.
4

UpperCamelCase Cho Type

UpperCamelCase (còn gọi PascalCase trong cộng đồng .NET) áp dụng cho mọi thứ ở "type level": struct, enum, trait, type alias, type parameter generic, và enum variant.

// Struct: UpperCamelCase
struct User {
    id: u64,
    name: String,
}

struct HttpRequest {
    method: String,
    path: String,
}

// Enum và variant: UpperCamelCase
enum HttpStatus {
    Ok,
    NotFound,
    InternalServerError,
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(u8, u8, u8),
}

// Trait: UpperCamelCase
trait Display {
    fn fmt(&self) -> String;
}

trait DatabasePool {
    fn acquire(&self) -> Connection;
}

// Type alias: UpperCamelCase
type UserId = u64;
type ConnectionPool = std::sync::Arc<DatabasePool>;

// Generic type parameter: UpperCamelCase, ngắn 1-2 chữ
fn process<T: Clone, U>(t: T, u: U) {
    // T, U là single uppercase letter - idiom phổ biến nhất
}

// Với generic mang tính semantic, dùng tên dài hơn cũng được
fn collect_into<Iter: Iterator>(it: Iter) {
    // Iter rõ nghĩa hơn T khi context phức tạp
}

struct HashMap<K, V> {
    // K = Key, V = Value - idiom stdlib
    _k: K,
    _v: V,
}

Quy ước cho generic parameter trong stdlib: T (Type), E (Error), K (Key), V (Value), I (Iterator), F (Function/Closure). Tên dài như Iter, Item, Output dùng khi cần nghĩa rõ ràng. Tránh tên 1 chữ vô nghĩa kiểu X, A, B trong public API.

Enum variant rất hay bị nhầm sang snake_case (vì giống "value"). Đây là nhầm phổ biến nhất của người từ Java/C++: trong Rust, variant là constructor của một type nên dùng UpperCamelCase: Some, None, Ok, Err — không phải some hay OK.

5

SCREAMING_SNAKE_CASE Cho Const Và Static

Mọi conststatic dùng SCREAMING_SNAKE_CASE — chữ in hoa, các từ phân tách bằng _. Đã quen từ bài 2829, đây là dịp tổng kết lại.

// const: SCREAMING_SNAKE_CASE
const MAX_RETRIES: u32 = 3;
const PI: f64 = 3.141592653589793;
const DEFAULT_TIMEOUT_SECS: u64 = 30;
const API_BASE_URL: &str = "https://api.blogcode.vn";

// static: SCREAMING_SNAKE_CASE
static APP_NAME: &str = "blogcode";
static APP_VERSION: &str = "1.0.0";
static GLOBAL_COUNTER: std::sync::atomic::AtomicU64
    = std::sync::atomic::AtomicU64::new(0);

// Enum variant "constant-like" trong impl - vẫn SCREAMING_SNAKE_CASE
struct Color;
impl Color {
    pub const RED: (u8, u8, u8) = (255, 0, 0);
    pub const GREEN: (u8, u8, u8) = (0, 255, 0);
    pub const BLUE: (u8, u8, u8) = (0, 0, 255);
}

fn main() {
    println!("Max retries: {MAX_RETRIES}");
    println!("App: {APP_NAME} v{APP_VERSION}");
    println!("Red: {:?}", Color::RED);
}

Logic của convention: const/static là giá trị bất biến ở phạm vi global hoặc associated với type — visually phân biệt với binding cục bộ (snake_case) và type (UpperCamelCase). Khi đọc code thấy MAX_RETRIES, bạn lập tức biết đây là hằng số, không phải biến, không phải type.

Compiler có lint riêng non_upper_case_globals cảnh báo nếu const/static không viết hoa toàn bộ — vd const maxRetries: u32 = 3; sẽ bị warning.

6

Crate Và Package Name

Đây là nơi gây nhầm lẫn nhiều nhất cho người mới: crate namepackage name là hai khái niệm khác nhau, và có convention riêng.

  • Crate nameidentifier dùng trong code Rust — xuất hiện trong use ...;, extern crate ...;. Vì là Rust identifier nên bị giới hạn ký tự: chỉ [a-z0-9_]. Idiomatic: lowercase, ngắn, không có separator hoặc dùng snake_case nếu cần. Ví dụ: tokio, serde, axum, reqwest, actix_web.
  • Package nametên ghi trong field name của [package] trong Cargo.toml, đồng thời là tên sẽ publish lên crates.io. Cargo cho phép dùng kebab-case (gạch ngang) — đẹp hơn cho URL, SEO, và đọc.

Cấu hình mẫu:

[package]
name = "my-web-app"      # package name - kebab-case OK
version = "0.1.0"
edition = "2024"

[lib]
name = "my_web_app"      # crate name (lib) - snake_case, dùng trong code
path = "src/lib.rs"

[[bin]]
name = "server"          # binary name - snake_case
path = "src/bin/server.rs"

[dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }

Cơ chế dịch tên: khi package name là my-web-app mà bạn không khai báo [lib] riêng, Cargo tự convert dấu - thành _ cho crate identifier. Khi import bạn viết:

// package name trong Cargo.toml: "my-web-app"
// crate identifier khi import: my_web_app
use my_web_app::routes;
use my_web_app::config::AppConfig;

Lưu ý conflict: nếu package foo-bar và package foo_bar cùng tồn tại trên crates.io, cả hai sẽ convert thành identifier foo_bar trong code — gây xung đột. Để tránh việc này, crates.io cấm publish hai package mà tên chỉ khác nhau ở dấu -_.

Khuyến nghị thực tế cho project Việt: package name nên ngắn, lowercase, có thể kebab-case (vd blogcode-auth, blogcode-pay); tránh tên gây xung đột với crate stdlib hoặc crate phổ biến.

7

File Và Folder Name

File và folder trong Rust project tuân theo snake_case — vì chúng map trực tiếp sang module identifier.

// Cấu trúc folder tiêu biểu
// src/
//   main.rs
//   lib.rs
//   config.rs              -- mod config; trong lib.rs
//   auth_middleware.rs     -- mod auth_middleware;
//   payment_gateway.rs     -- mod payment_gateway;
//   routes/
//     mod.rs               -- hoặc routes.rs (Rust 2018+)
//     auth.rs              -- mod auth; trong routes
//     payment.rs           -- mod payment; trong routes
// tests/
//   integration_test.rs    -- snake_case
//   auth_flow_test.rs
// examples/
//   web_server.rs          -- chạy qua cargo run --example web_server
//   simple_client.rs
// benches/
//   parser_bench.rs

// Trong lib.rs:
mod config;
mod auth_middleware;
mod payment_gateway;
mod routes;

Một số quy tắc bổ sung:

  • File luôn có đuôi .rs, tên file luôn snake_case (không UpperCamelCase kiểu UserService.rs).
  • Folder module dùng snake_case: src/auth/, src/payment_gateway/.
  • Test file: thư mục tests/ chứa integration test, mỗi file là một test crate riêng — tests/integration_test.rs, tests/auth_flow_test.rs.
  • Example file: thư mục examples/ chứa binary minh hoạ — examples/web_server.rs chạy qua cargo run --example web_server.
  • File đặc biệt giữ nguyên: Cargo.toml, Cargo.lock, README.md, LICENSE theo convention của Cargo và open-source community — không Rust-specific.
8

Acronym Trong Tên

Acronym (HTTP, JSON, XML, URL, IO, UUID...) gây nhầm lẫn lớn nhất khi đặt tên type. RFC 430 và Rust API Guidelines chốt rõ: treat acronym như word thường — chỉ chữ cái đầu viết hoa.

// ĐÚNG - acronym treat như word thường
struct HttpClient { /* ... */ }
struct JsonValue { /* ... */ }
struct UrlParser { /* ... */ }
struct UuidGenerator { /* ... */ }
struct XmlHttpRequest { /* ... */ }  // 3 acronym ghép, vẫn chỉ chữ đầu hoa
struct IoError { /* ... */ }
struct DbConnection { /* ... */ }

trait AsyncRead { /* ... */ }
trait FromStr { /* ... */ }
type ApiResult<T> = Result<T, ApiError>;

// SAI - clippy sẽ warning non_camel_case_types nếu >= 3 chữ hoa liên tiếp
struct HTTPClient { /* ... */ }           // warning
struct JSONValue { /* ... */ }            // warning
struct XMLHTTPRequest { /* ... */ }       // warning
struct URLParser { /* ... */ }            // warning

// Acronym 2 chữ vẫn nên treat đồng nhất - giữ thói quen
struct IoError { /* ... */ }              // tốt hơn IOError
struct DbConnection { /* ... */ }         // tốt hơn DBConnection

Quy tắc thực tế: nhìn vào stdlib và crate phổ biến để học idiom. Vài ví dụ:

  • Stdlib: std::io::Error (không std::IO::Error), std::net::TcpStream, std::net::UdpSocket.
  • Crate phổ biến: reqwest::Client, serde_json::Value, http::Request, uuid::Uuid.
  • Trait stdlib: FromStr, AsRef, AsMut — pattern AsX, FromX.

Idiom Rust nghiêm hơn Java/C# — nơi HTTPClient, XMLParser được chấp nhận rộng rãi. Người chuyển từ Java sang Rust thường mất vài tuần để quen với HttpClient. Đây không chỉ là quy ước thẩm mỹ — clippy thực sự sẽ warning nếu phát hiện 3+ chữ hoa liên tiếp trong type name (vi phạm CamelCase chính thức).

9

Clippy Enforce Naming

Rust không chỉ "khuyến nghị" convention — rustc tích hợp sẵn ba lint bật mặc định để cảnh báo khi sai:

  • non_snake_case — variable, function, method, module không phải snake_case.
  • non_camel_case_types — struct, enum, trait, type alias, type parameter không phải UpperCamelCase.
  • non_upper_case_globals — const, static không phải SCREAMING_SNAKE_CASE.
// Compile sẽ in warning, nhưng vẫn build được
fn ProcessOrder() {}  // warning: non_snake_case
                      // help: convert to snake_case as `process_order`

let UserName = "An";  // warning: non_snake_case
                      // help: convert to snake_case as `user_name`

struct http_client {} // warning: non_camel_case_types
                      // help: convert to UpperCamelCase as `HttpClient`

const max_retries: u32 = 3;  // warning: non_upper_case_globals
                              // help: convert to upper case as `MAX_RETRIES`

// Cách silence hợp lý: CHỈ khi có lý do chính đáng
// Ví dụ điển hình: FFI binding cần match C symbol nguyên bản
#[allow(non_snake_case)]
extern "C" {
    fn GetUserName(buffer: *mut u8, size: u32) -> i32;  // Win32 API
    fn CreateFileW(/* ... */) -> *mut std::ffi::c_void; // Win32 API
}

#[allow(non_camel_case_types)]
#[repr(C)]
struct sqlite3 {                // SQLite native struct - giữ nguyên tên C
    _opaque: [u8; 0],
}

#[allow(non_upper_case_globals)]
const PI_lowercase: f64 = 3.14;  // KHÔNG nên - không có lý do

Quy tắc dùng #[allow(...)]:

  • OK: FFI binding cần khớp tên C symbol nguyên bản (Win32 API GetUserName, SQLite struct sqlite3, OpenSSL SSL_CTX).
  • OK: tự sinh code (macro, build script) tạo identifier không kiểm soát được — vd bindgen sinh tên đúng theo C header.
  • KHÔNG OK: code của bạn viết tay nhưng "tôi thích viết HTTPClient hơn HttpClient" — đây là vi phạm idiom không có lý do, reviewer sẽ block.
  • KHÔNG OK: copy code từ Java/C# qua mà lười rename — phải refactor về Rust naming khi merge vào codebase.

Để enforce nghiêm hơn, project production thường thêm vào Cargo.toml:

[lints.rust]
non_snake_case = "deny"
non_camel_case_types = "deny"
non_upper_case_globals = "deny"

Chuyển từ warn (mặc định) sang deny — sai convention sẽ làm fail build, không cho merge code dirty vào main branch.

10

Tổng Kết

Tổng kết Nhóm 4 - Variables & Mutability:

  • Bài 25: let tạo binding, mặc định immutable, type inference mạnh.
  • Bài 26: let mut opt-in mutable; type không đổi.
  • Bài 27: shadowing tạo binding mới cùng tên, được đổi type, khác mut.
  • Bài 28: const là hằng số compile-time, inlined tại use site, bắt buộc type annotation.
  • Bài 29: static là biến toàn cục có địa chỉ ổn định, 'static lifetime, mutable cần unsafe.
  • Bài 30 (bài này): convention đặt tên RFC 430 — snake_case / UpperCamelCase / SCREAMING_SNAKE_CASE, package vs crate name, acronym, clippy enforce.

Sau Nhóm 4 bạn đã đủ kiến thức về cách lưu trữ giá trị trong Rust. Nhóm 5 sẽ đào sâu các giá trị đó có thể là gì — bắt đầu với scalar types (integer, float, bool, char).

11

Bài Tập Củng Cố

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

  1. Đoạn code sau có 4 vi phạm convention. Liệt kê và sửa: const max_size: u32 = 100; struct user_profile { Name: String, Age_Years: u32 } fn GetUser(ID: u64) {}
  2. Bạn có package tên my-auth-service trong Cargo.toml. Khi import vào file src/main.rs của crate khác, bạn viết use ?::middleware; — điền vào dấu ?. Giải thích vì sao.
  3. Tên type nào đúng convention Rust? Cho mỗi tên sai, đề xuất tên đúng: JSONParser, HttpRequest, SQLConnection, UrlEncoder, XMLHTTPClient.
  4. Khi nào việc dùng #[allow(non_snake_case)] là chính đáng? Cho 1 ví dụ chính đáng và 1 ví dụ không nên dùng.
  5. Generic parameter trong stdlib thường dùng tên 1 chữ (T, K, V, E). Khi nào nên dùng tên dài hơn (Iter, Item, Output)?
Đáp án
  1. 4 vi phạm: (a) max_size phải MAX_SIZE (const → SCREAMING_SNAKE_CASE). (b) user_profile phải UserProfile (struct → UpperCamelCase). (c) Field NameAge_Years phải name, age_years (field → snake_case). (d) GetUser(ID: u64) phải fn get_user(id: u64) (function và parameter → snake_case). Bản đúng: const MAX_SIZE: u32 = 100; struct UserProfile { name: String, age_years: u32 } fn get_user(id: u64) {}.
  2. use my_auth_service::middleware;. Cargo tự convert dấu - trong package name sang dấu _ để tạo crate identifier hợp lệ trong Rust (vì identifier không cho phép -). Package name my-auth-service trong Cargo.toml = crate identifier my_auth_service trong code.
  3. Đúng: HttpRequest, UrlEncoder. Sai (đề xuất sửa): JSONParserJsonParser; SQLConnectionSqlConnection; XMLHTTPClientXmlHttpClient. Quy tắc: acronym treat như word thường, chỉ chữ đầu viết hoa.
  4. Chính đáng: FFI binding cần khớp tên C symbol nguyên bản, vd extern "C" { fn GetUserName(...); } để link đúng symbol Win32 API. Không nên: code Rust thuần do bạn viết tay nhưng muốn giữ tên kiểu Java (fn GetUser()) — vi phạm idiom không có lý do, reviewer sẽ yêu cầu rename.
  5. Dùng tên 1 chữ khi nghĩa là "type bất kỳ" trong context đơn giản và đã rõ từ tên function/struct. Dùng tên dài (Iter, Item, Output, Pool) khi (a) signature có nhiều generic parameter dễ nhầm; (b) tên mang semantic giúp đọc tốt hơn (vd fn collect<Iter: Iterator>(it: Iter) rõ hơn <T: Iterator>(it: T)); (c) public API cần documentation rõ ràng.
12

Bài Tiếp Theo

Bài 31: Integer Có Dấu: i8, i16, i32, i64, i128, isize — mở đầu Nhóm 5 (Scalar Types) với 6 size integer có dấu, range mỗi loại, vì sao i32 là default, isize phụ thuộc target, cách dùng literal với separator (1_000_000), prefix (0xff, 0b1010, 0o77), và suffix kiểu (1i64).