Mục lục
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à
HttpClientchứ không phảiHTTPClient. - Hiểu các clippy lint
non_snake_case,non_camel_case_types,non_upper_case_globalstự độ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.
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).
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
camelCasekiểuuserName— compiler sẽ warningnon_snake_case(mục 9). - Không viết tắt khó hiểu:
usr_nmtệ,user_nametốt;num_retrieschấp nhận được,n_rkhông. - Function trả về
boolthường bắt đầu bằngis_,has_,can_:is_empty(),has_permission(),can_edit(). - Tên file Rust source chính là tên module —
auth_middleware.rstự động trở thànhmod auth_middlewarekhimod auth_middleware;được khai báo ở file cha.
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.
SCREAMING_SNAKE_CASE Cho Const Và Static
Mọi const và static dùng SCREAMING_SNAKE_CASE — chữ in hoa, các từ phân tách bằng _. Đã quen từ bài 28 và 29, đâ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.
Crate Và Package Name
Đây là nơi gây nhầm lẫn nhiều nhất cho người mới: crate name và package name là hai khái niệm khác nhau, và có convention riêng.
- Crate name là identifier 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ùngsnake_casenếu cần. Ví dụ:tokio,serde,axum,reqwest,actix_web. - Package name là tên ghi trong field
namecủa[package]trongCargo.toml, đồng thời là tên sẽ publish lên crates.io. Cargo cho phép dùngkebab-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 - và _.
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.
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ểuUserService.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.rschạy quacargo run --example web_server. - File đặc biệt giữ nguyên:
Cargo.toml,Cargo.lock,README.md,LICENSEtheo convention của Cargo và open-source community — không Rust-specific.
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ôngstd::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— patternAsX,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).
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 structsqlite3, OpenSSLSSL_CTX). - OK: tự sinh code (macro, build script) tạo identifier không kiểm soát được — vd
bindgensinh 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.
Tổng Kết
Tổng kết Nhóm 4 - Variables & Mutability:
- Bài 25:
lettạo binding, mặc định immutable, type inference mạnh. - Bài 26:
let mutopt-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:
constlà hằng số compile-time, inlined tại use site, bắt buộc type annotation. - Bài 29:
staticlà biến toàn cục có địa chỉ ổn định,'staticlifetime, mutable cầnunsafe. - 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).
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Đ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) {} - Bạn có package tên
my-auth-servicetrongCargo.toml. Khi import vào filesrc/main.rscủa crate khác, bạn viếtuse ?::middleware;— điền vào dấu?. Giải thích vì sao. - Tên type nào đúng convention Rust? Cho mỗi tên sai, đề xuất tên đúng:
JSONParser,HttpRequest,SQLConnection,UrlEncoder,XMLHTTPClient. - 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. - 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
- 4 vi phạm: (a)
max_sizephảiMAX_SIZE(const → SCREAMING_SNAKE_CASE). (b)user_profilephảiUserProfile(struct → UpperCamelCase). (c) FieldNamevàAge_Yearsphảiname,age_years(field → snake_case). (d)GetUser(ID: u64)phảifn 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) {}. 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 namemy-auth-servicetrongCargo.toml= crate identifiermy_auth_servicetrong code.- Đúng:
HttpRequest,UrlEncoder. Sai (đề xuất sửa):JSONParser→JsonParser;SQLConnection→SqlConnection;XMLHTTPClient→XmlHttpClient. Quy tắc: acronym treat như word thường, chỉ chữ đầu viết hoa. - 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. - 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 (vdfn collect<Iter: Iterator>(it: Iter)rõ hơn<T: Iterator>(it: T)); (c) public API cần documentation rõ ràng.
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).
