Danh sách bài viết

Bài 20: Cargo.toml Anatomy: [package], [dependencies], [features], edition

Bài 20 của series Rust Cơ Bản — mổ xẻ chi tiết file Cargo.toml. Bạn sẽ hiểu format TOML, từng field trong [package] (name, version SemVer, edition 2024, rust-version MSRV, license SPDX, keywords, categories), ba dạng khai báo dependency (shorthand, full table, git, path), khác biệt giữa [dependencies], [dev-dependencies], [build-dependencies], cơ chế [features] với optional + dep:, target table [[bin]] [[example]] [[bench]], profile dev / release với LTO, và preview [workspace] đa crate. Kết bài có một Cargo.toml axum + sqlx hoàn chỉnh để copy đi dùng.

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

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.toml nà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ùng cfg(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.toml mẫ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.

2

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", integer 42, float 1.5, bool true, 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ảng bin.
  • Case sensitive: Name khác name.
  • 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.

3

[package] Section Bắt Buộc

Mọi package Rust phải có [package]. Hai field tối thiểu là nameversion; 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.0 là 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ánh license-file nế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".

4

[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 = false tắ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ào Cargo.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ó version kèm theo).
  • Có thể combine: my-crate = { version = "1.0", path = "../my-crate" } — local khi dev, version khi publish.
5

[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, build tests/, 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][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.

6

[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.

7

[[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ự handle fn 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.
8

[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. 1 giúp LTO hiệu quả tối đa nhưng build chậm hơn (default release là 16).
  • strip: true bỏ 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.

9

[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.

10

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.

11

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ộc name + version; nên có edition, rust-version (MSRV), license SPDX, description, repository, keywords (≤5), categories.
  • [dependencies] hỗ trợ shorthand "1", full table với features / default-features, git source, path source.
  • [dev-dependencies] cho test/example/bench; [build-dependencies] cho build.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 = false là option thường dùng.
  • [profile.release] với lto = "thin", codegen-units = 1, strip = true, panic = "abort" cho binary nhỏ và nhanh nhất.
  • [workspace] với members + [workspace.dependencies] giúp đa crate share version — học sâu ở Nhóm 32.
12

Bài Tập Củng Cố

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

  1. Một crate khai báo serde = "1.0.150". Cargo có cho phép resolve sang version 1.2.5 không? Vì sao? Còn 2.0.0?
  2. Bạn viết library và đặt tempfile = "3" trong [dependencies]. Consumer dùng library có phải tải tempfile không? Nên đặt ở đâu cho đúng?
  3. Khai báo feature cache bật optional dependency redis. Hai dòng nào cần thêm vào [dependencies][features]?
  4. 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.
  5. Repo có 3 crate api, core, cli. Mỗi crate đều cần tokio, serde. Cách tổ chức Cargo.toml nào tránh khai báo version 3 lần?
Đáp án
  1. "1.0.150" ngầm là "^1.0.150" — Cargo cho phép mọi version >=1.0.150, <2.0.0, nên 1.2.5 OK. Còn 2.0.0 không tương thích (vượt MAJOR), bị từ chối.
  2. Có, vì [dependencies] compile vào binary chính. tempfile chỉ dùng cho test → nên chuyển sang [dev-dependencies] để consumer không phải tải.
  3. 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.
  4. opt-level = "z" (hoặc giữ 3 nế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 qua default-features = false.
  5. Tạo workspace ở root: [workspace] members = ["api", "core", "cli"] + [workspace.dependencies] tokio = "1.40", serde = { version = "1", features = ["derive"] }. Mỗi crate dùng tokio.workspace = true, serde.workspace = true.
13

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--bin / --example để workflow dev mượt hơn.