Mục lục
- Mục Tiêu Bài Học
- Format TOML 30 Giây
- [package] Section Bắt Buộc
- [dependencies] — Ba Dạng Khai Báo
- [dev-dependencies] & [build-dependencies]
- [features] Flag
- [[bin]], [[example]], [[bench]] Target Tables
- [profile.dev] và [profile.release]
- [workspace] Preview
- Cargo.toml Hoàn Chỉnh Mẫu
- Tổng Kết
- Bài Tập Củng Cố
- Bài Tiếp Theo
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Đọc trôi chảy bất kỳ file
Cargo.tomlnào, biết từng section dùng để làm gì. - Nắm rõ field bắt buộc trong
[package]và quy tắc đặt SemVer, license SPDX, MSRV. - Phân biệt 3 dạng khai báo dependency (shorthand, full table, git, path) và lúc nào nên dùng cái nào.
- Hiểu khác biệt
[dependencies]vs[dev-dependencies]vs[build-dependencies]. - Biết khai báo
[features]flag với optional dependency và dùngcfg(feature = "x")trong code. - Customize target qua
[[bin]],[[example]],[[bench]]. - Tinh chỉnh build qua
[profile.release](LTO, codegen-units, strip). - Có sẵn một
Cargo.tomlmẫu cho web API project để dùng ngay.
Bài Bài 19: Cấu Trúc Thư Mục Mặc Định Của Cargo Project đã giới thiệu sơ lược Cargo.toml. Bài này đi sâu vào từng section.
Format TOML 30 Giây
Cargo.toml viết bằng TOML — Tom's Obvious Minimal Language, do Tom Preston-Werner (đồng sáng lập GitHub) thiết kế. Mục tiêu: dễ đọc cho người, dễ parse cho máy, ít ambiguous hơn YAML.
Cú pháp gói gọn trong vài quy tắc:
- key = value: dòng đơn giản nhất, value có thể là string
"abc", integer42, float1.5, booltrue, array["a", "b"], inline table{ k = "v" }. - [section]: khai báo một table, các key sau đó thuộc table này cho tới khi gặp table mới.
- [[array]]: array of tables — mỗi lần khai báo
[[bin]]là thêm một phần tử vào mảngbin. - Case sensitive:
Namekhácname. - Comment: bắt đầu bằng
#, tới hết dòng. - Multi-line string: bọc trong
"""...""", giữ nguyên xuống dòng.
# Comment đầu file
title = "Demo TOML"
count = 42
enabled = true
tags = ["rust", "cargo"]
[server]
host = "127.0.0.1"
port = 8080
[server.tls]
enabled = false
[[admins]]
name = "An"
email = "[email protected]"
[[admins]]
name = "Binh"
email = "[email protected]"
description = """
Đây là multi-line.
Giữ nguyên ngắt dòng.
"""
Nắm 6 luật trên là đủ đọc Cargo.toml. Spec đầy đủ ở toml.io.
[package] Section Bắt Buộc
Mọi package Rust phải có [package]. Hai field tối thiểu là name và version; edition mặc định 2015 nếu thiếu (nên luôn khai báo).
[package]
name = "my-api"
version = "0.1.0"
edition = "2024"
rust-version = "1.83"
authors = ["Canh NV <[email protected]>"]
description = "Một REST API ví dụ viết bằng axum"
license = "MIT OR Apache-2.0"
repository = "https://github.com/blogcodevn/my-api"
homepage = "https://blogcode.vn"
documentation = "https://docs.rs/my-api"
readme = "README.md"
keywords = ["axum", "web", "api", "rest"]
categories = ["web-programming::http-server"]
Giải thích từng field:
- name: kebab-case identifier, dùng làm tên crate trên crates.io. Phải unique nếu publish.
- version: tuân theo SemVer
MAJOR.MINOR.PATCH.0.1.0là pre-1.0, breaking change nằm ở MINOR (Cargo coi 0.x.y khác 0.(x+1).0 là breaking). - edition:
"2024"là mới nhất. Edition không break compiler — chỉ thay đổi cú pháp/idiom cho crate đó. - rust-version: MSRV (Minimum Supported Rust Version). Cargo ≥1.56 sẽ từ chối build nếu toolchain cũ hơn. Giúp consumer biết toolchain tối thiểu cần có.
- authors: mảng string dạng
"Name <email>". Optional, ít quan trọng từ Rust 2024. - description: 1 câu mô tả, hiển thị trên crates.io search.
- license: SPDX expression. Phổ biến nhất là
"MIT OR Apache-2.0"(dual license, consumer chọn). Tránhlicense-filenếu có thể. - repository / homepage / documentation: URL, hiển thị trên crates.io.
- readme: đường dẫn file README, default tự tìm
README.md. - keywords: tối đa 5 từ khoá, mỗi cái ≤20 ký tự, dùng cho search trên crates.io.
- categories: chọn từ danh sách categories chính thức của crates.io. Sai slug → publish fail.
Pitfall: license = "MIT" (không có Apache-2.0) khiến crate không tương thích với một số dependency phổ biến trong ecosystem Rust. Mặc định nên dùng dual "MIT OR Apache-2.0".
[dependencies] — Ba Dạng Khai Báo
[dependencies] liệt kê các crate runtime (compile vào binary chính). Cargo hỗ trợ 4 source: crates.io (mặc định), git, path, và alternative registry. Mỗi dependency có 3 form viết:
[dependencies]
# 1. Shorthand — chỉ ghi version, ngầm hiểu caret ^1.x.x
serde = "1"
# 2. Full table — version + features + flag
tokio = { version = "1.40", features = ["full"] }
axum = { version = "0.7", default-features = false, features = ["json", "tokio"] }
# Optional dependency (chỉ enable khi feature bật)
redis = { version = "0.27", optional = true }
# 3. Git source — chỉ branch / tag / rev
my-fork = { git = "https://github.com/me/my-fork", branch = "feat/x" }
tokio-dev = { git = "https://github.com/tokio-rs/tokio", rev = "abc1234" }
# 4. Path local — dùng trong workspace hoặc monorepo
core = { path = "../core" }
Một số chi tiết hay quên:
"1"ngầm là"^1"tức "any compatible với 1.0.0, không vượt 2.0.0". Tương đương^1.x.y."=1.2.3"pin chính xác version — chỉ dùng khi thật cần (debug bug version)."~1.2"chỉ cho phép thay PATCH, không thay MINOR.default-features = falsetắt feature mặc định, sau đó tự chọn feature cần — pattern phổ biến khi muốn giảm binary size.git+branch/tag/rev: dùng khi cần hotfix trước khi upstream release. Cargo sẽ lock commit SHA vàoCargo.lock.path: hữu ích khi develop song song crate local; nếu publish lên crates.io thì path bị bỏ qua (phải cóversionkèm theo).- Có thể combine:
my-crate = { version = "1.0", path = "../my-crate" }— local khi dev, version khi publish.
[dev-dependencies] & [build-dependencies]
Cargo phân chia dependency theo khi nào cần dùng, không chỉ cần dùng cái gì.
- [dependencies]: compile vào binary/library chính. Consumer sẽ tải về.
- [dev-dependencies]: chỉ dùng khi chạy
cargo test, buildtests/,examples/,benches/. Không compile vào release binary. Consumer của library không cần tải. - [build-dependencies]: chỉ dùng bởi
build.rs(build script chạy trước compile crate chính). Ví dụ: generate code, link C library, embed git hash.
[dev-dependencies]
tempfile = "3"
criterion = { version = "0.5", features = ["html_reports"] }
mockito = "1"
pretty_assertions = "1"
[build-dependencies]
cc = "1" # compile C/C++ code kèm theo
prost-build = "0.13" # gen Rust code từ .proto
vergen = { version = "9", features = ["git", "gitcl"] }
Cùng một crate có thể xuất hiện ở cả [dependencies] và [dev-dependencies] — ví dụ tokio dùng feature rt-multi-thread khi runtime, nhưng test cần thêm feature test-util. Cargo sẽ merge feature lúc build test.
Pitfall: đặt tempfile, criterion nhầm vào [dependencies] sẽ làm consumer phải tải về vô ích, tăng compile time downstream.
[features] Flag
Feature là cờ compile-time để bật/tắt code và dependency. Cho phép crate có nhiều variant mà không cần fork.
[dependencies]
serde = { version = "1", optional = true }
redis = { version = "0.27", optional = true }
jsonwebtoken = { version = "9", optional = true }
[features]
default = ["json"]
json = ["dep:serde"]
cache = ["dep:redis"]
jwt = ["dep:jsonwebtoken"]
full = ["json", "cache", "jwt"]
Cú pháp quan trọng:
- default: feature tự bật khi consumer thêm dependency. User tắt qua
default-features = false. - optional = true: dependency không tự kéo về, chờ feature enable.
- "dep:xxx" (Rust 2021+, cargo ≥1.60): explicit kéo optional dependency, tránh tự sinh feature implicit cùng tên.
- Combo feature:
full = ["json", "cache", "jwt"]— bật một loạt feature khác.
Trong code, dùng cfg để rẽ nhánh:
#[cfg(feature = "cache")]
pub mod cache;
#[cfg(feature = "jwt")]
pub fn verify_token(token: &str) -> bool {
// chỉ compile khi feature jwt bật
jsonwebtoken::decode::<Claims>(token, &KEY, &Validation::default()).is_ok()
}
Consumer bật feature trong Cargo.toml của họ:
my-api = { version = "0.1", features = ["jwt", "cache"] }
Tip: chạy cargo build --all-features để test mọi tổ hợp; CI nên có matrix build với từng feature riêng để bắt lỗi cfg.
[[bin]], [[example]], [[bench]] Target Tables
Cargo tự discover target theo convention: src/main.rs → bin cùng tên package, src/bin/*.rs → bin theo tên file, examples/*.rs → example, benches/*.rs → bench. Khi cần override path hoặc thêm config riêng, dùng array of tables.
[[bin]]
name = "server"
path = "src/main.rs"
[[bin]]
name = "worker"
path = "src/bin/worker.rs"
required-features = ["queue"]
[[bin]]
name = "migrate"
path = "src/bin/migrate.rs"
[[example]]
name = "smoke"
path = "examples/smoke.rs"
[[example]]
name = "load-test"
path = "examples/load_test.rs"
required-features = ["full"]
[[bench]]
name = "parse-bench"
path = "benches/parse.rs"
harness = false # dùng criterion thay vì libtest mặc định
Một số option đáng nhớ:
required-features: bin/example chỉ build khi feature đó enable.harness = false: tắt test harness mặc định, để criterion (hoặc framework custom khác) tự handlefn main.test = false/doctest = false: bỏ qua target khi chạy test.- Chạy bin cụ thể:
cargo run --bin worker; example:cargo run --example smoke.
[profile.dev] và [profile.release]
Profile điều khiển flag mà Cargo truyền cho rustc. Có 4 profile built-in: dev (default cho cargo build), release (cho cargo build --release), test, bench.
[profile.dev]
opt-level = 0
debug = true
incremental = true
overflow-checks = true
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = true
panic = "abort"
debug = false
# Profile riêng cho benchmark, kế thừa release
[profile.bench]
inherits = "release"
debug = true
Các field thường tinh chỉnh:
- opt-level:
0(no opt, build nhanh),1,2,3(max),"s"(size),"z"(max size). - debug:
true/false/"line-tables-only"— control DWARF info. - lto:
false(off),"thin"(cân bằng, recommended),true/"fat"(max optimization, build cực chậm). - codegen-units: số codegen unit song song.
1giúp LTO hiệu quả tối đa nhưng build chậm hơn (default release là 16). - strip:
truebỏ symbol khỏi binary → giảm size mạnh. - panic:
"unwind"(default) hoặc"abort"(bỏ unwind, binary nhỏ hơn, không catch_unwind được). - incremental: bật cho dev để rebuild nhanh; tắt cho release để LTO sạch.
Cấu hình release trên đây tạo binary nhỏ và nhanh nhất, đổi lại build time có thể lâu gấp 5-10 lần.
[workspace] Preview
Khi project có nhiều crate trong cùng repo (api + core + cli + worker), dùng [workspace] để share Cargo.lock và folder target/.
# Cargo.toml ở root, không phải package
[workspace]
resolver = "2"
members = ["api", "core", "cli", "worker"]
[workspace.package]
version = "0.1.0"
edition = "2024"
rust-version = "1.83"
license = "MIT OR Apache-2.0"
[workspace.dependencies]
tokio = { version = "1.40", features = ["full"] }
serde = { version = "1", features = ["derive"] }
anyhow = "1"
Mỗi member sau đó tham chiếu version chung:
# api/Cargo.toml
[package]
name = "api"
version.workspace = true
edition.workspace = true
license.workspace = true
[dependencies]
tokio.workspace = true
serde.workspace = true
core = { path = "../core" }
Workspace giúp tránh duplicate version trên cùng crate (giảm compile time). resolver = "2" là feature resolver mới, default cho edition 2021+. Bài sâu hơn về workspace sẽ ở Nhóm 32 — Cargo Nâng Cao.
Cargo.toml Hoàn Chỉnh Mẫu
Tổng hợp lại tất cả section thành một Cargo.toml cho web API project axum + sqlx + tracing — sẵn sàng copy đi dùng.
[package]
name = "my-api"
version = "0.1.0"
edition = "2024"
rust-version = "1.83"
authors = ["Canh NV <[email protected]>"]
description = "REST API mẫu viết bằng axum + sqlx + tracing"
license = "MIT OR Apache-2.0"
repository = "https://github.com/blogcodevn/my-api"
homepage = "https://blogcode.vn"
readme = "README.md"
keywords = ["axum", "sqlx", "web", "api", "rest"]
categories = ["web-programming::http-server"]
[dependencies]
# HTTP framework
axum = { version = "0.7", features = ["macros", "json"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["trace", "cors"] }
# Async runtime
tokio = { version = "1.40", features = ["full"] }
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Database
sqlx = { version = "0.8", default-features = false, features = [
"runtime-tokio-rustls",
"postgres",
"macros",
"chrono",
"uuid",
] }
# Observability
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
# Error handling
anyhow = "1"
thiserror = "2"
# Optional integrations
redis = { version = "0.27", optional = true }
jsonwebtoken = { version = "9", optional = true }
[dev-dependencies]
tokio = { version = "1.40", features = ["test-util"] }
tempfile = "3"
criterion = { version = "0.5", features = ["html_reports"] }
mockito = "1"
pretty_assertions = "1"
[build-dependencies]
vergen = { version = "9", features = ["git", "gitcl"] }
[features]
default = ["jwt"]
cache = ["dep:redis"]
jwt = ["dep:jsonwebtoken"]
full = ["cache", "jwt"]
[[bin]]
name = "server"
path = "src/main.rs"
[[bin]]
name = "migrate"
path = "src/bin/migrate.rs"
[[example]]
name = "smoke"
path = "examples/smoke.rs"
[[bench]]
name = "router-bench"
path = "benches/router.rs"
harness = false
[profile.dev]
opt-level = 0
debug = true
incremental = true
[profile.release]
opt-level = 3
lto = "thin"
codegen-units = 1
strip = true
panic = "abort"
[profile.bench]
inherits = "release"
debug = true
File này thể hiện gần như mọi pattern bạn sẽ gặp trong production Rust web project.
Tổng Kết
- Cargo.toml dùng TOML — 6 quy tắc cú pháp đủ để đọc/viết thông thạo.
[package]bắt buộcname+version; nên cóedition,rust-version(MSRV),licenseSPDX,description,repository,keywords(≤5),categories.[dependencies]hỗ trợ shorthand"1", full table vớifeatures/default-features, git source, path source.[dev-dependencies]cho test/example/bench;[build-dependencies]chobuild.rs. Cả hai không vào binary chính.[features]+optional = true+"dep:xxx"cho phép bật/tắt code và dependency compile-time.[[bin]] [[example]] [[bench]]override target convention;required-features,harness = falselà option thường dùng.[profile.release]vớilto = "thin",codegen-units = 1,strip = true,panic = "abort"cho binary nhỏ và nhanh nhất.[workspace]vớimembers+[workspace.dependencies]giúp đa crate share version — học sâu ở Nhóm 32.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Một crate khai báo
serde = "1.0.150". Cargo có cho phép resolve sang version1.2.5không? Vì sao? Còn2.0.0? - Bạn viết library và đặt
tempfile = "3"trong[dependencies]. Consumer dùng library có phải tảitempfilekhông? Nên đặt ở đâu cho đúng? - Khai báo feature
cachebật optional dependencyredis. Hai dòng nào cần thêm vào[dependencies]và[features]? - Một build release ra binary 30 MB, bạn muốn giảm xuống còn 8-10 MB mà giữ performance ổn. Liệt kê 4 field trong
[profile.release]cần chỉnh. - Repo có 3 crate
api,core,cli. Mỗi crate đều cầntokio,serde. Cách tổ chức Cargo.toml nào tránh khai báo version 3 lần?
Đáp án
"1.0.150"ngầm là"^1.0.150"— Cargo cho phép mọi version>=1.0.150, <2.0.0, nên1.2.5OK. Còn2.0.0không tương thích (vượt MAJOR), bị từ chối.- Có, vì
[dependencies]compile vào binary chính.tempfilechỉ dùng cho test → nên chuyển sang[dev-dependencies]để consumer không phải tải. - Trong
[dependencies]:redis = { version = "0.27", optional = true }. Trong[features]:cache = ["dep:redis"]. Tiền tốdep:giúp Cargo hiểu đây là optional dep chứ không tự sinh feature trùng tên. opt-level = "z"(hoặc giữ3nếu ưu tiên speed),lto = "thin"(hoặc"fat"),codegen-units = 1,strip = true,panic = "abort". Bonus: tắt feature không dùng quadefault-features = false.- Tạo workspace ở root:
[workspace] members = ["api", "core", "cli"]+[workspace.dependencies] tokio = "1.40", serde = { version = "1", features = ["derive"] }. Mỗi crate dùngtokio.workspace = true,serde.workspace = true.
Bài Tiếp Theo
Bài 21: cargo build vs cargo run vs cargo check — phân biệt 3 lệnh hay nhầm lẫn: cargo check typecheck siêu nhanh không gen binary, cargo build compile + link, cargo run build + execute, kèm flag --release và --bin / --example để workflow dev mượt hơn.
