Mục lục
- Mục Tiêu Bài Học
- cargo check — Kiểm Tra Nhanh Không Sinh Binary
- cargo build — Compile Thực Sự
- cargo run — Build + Execute Trong 1 Lệnh
- cargo build --release — Optimized Build
- Flag --bin Cho Multiple Binary
- Flag --example Cho Example Programs
- Flag --features Bật Feature
- Workflow Khuyến Nghị
- 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ẽ:
- Hiểu rõ
cargo checklàm gì và vì sao nó nhanh hơncargo build2-3 lần. - Biết khi nào dùng
cargo build(compile + link) vscargo run(build + execute liền). - Phân biệt profile
dev(default) vàrelease(--release), output path khác nhau. - Dùng flag
--binchọn binary cụ thể trong project có nhiều bin,--examplechạy demo trongexamples/. - Bật/tắt feature compile-time bằng
--featuresvà--no-default-features. - Có workflow dev khuyến nghị:
cargo checkliên tục khi gõ code →cargo runkhi test logic →cargo build --releasekhi đo bench / deploy. - Cài
cargo-watchđể auto rerun mỗi khi save file.
Bài Bài 20: Cargo.toml Anatomy đã xem profile dev / release trong manifest. Bài này tập trung vào lệnh CLI hằng ngày.
cargo check — Kiểm Tra Nhanh Không Sinh Binary
cargo check chạy front-end của rustc: typecheck, borrow check, name resolution, macro expansion. Nó KHÔNG chạy code generation (LLVM IR → object), KHÔNG link. Kết quả là không có executable; chỉ sinh metadata .rmeta + một số .rlib metadata trong target/debug/deps/ cùng với .cargo-lock.
Tại sao nhanh hơn build 2-3 lần? Vì 60-70% thời gian compile Rust nằm ở phase codegen + LTO + link. Bỏ qua chính những phase đó, check chỉ làm phần phân tích syntax/semantics.
$ time cargo check
Checking my-app v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.85s
real 0m0.92s
Đây cũng chính là lệnh mà rust-analyzer chạy ngầm liên tục trong IDE để báo lỗi real-time. Mỗi lần bạn save file, rust-analyzer phát hành một incremental cargo check để cập nhật diagnostic — đó là lý do red squiggle xuất hiện trong vài trăm millisecond chứ không phải vài giây.
Khi nào dùng: gõ code và muốn biết "có lỗi compile không?". Workflow lý tưởng là check liên tục, chỉ chuyển sang build/run khi đã sẵn sàng test logic thực tế.
Pitfall thường gặp: code pass cargo check không có nghĩa là chạy được — vì linker error (thiếu symbol, mismatched ABI) chỉ phát hiện ở phase link mà check bỏ qua. Tuy hiếm, vẫn nên chạy ít nhất 1 lần cargo build trước khi commit.
cargo build — Compile Thực Sự
cargo build chạy đầy đủ pipeline: parse → typecheck → borrow check → MIR → LLVM IR → object → link ra binary cuối. Mặc định dùng profile dev (opt-level = 0, full debug info), output ở target/debug/<package-name>.
$ time cargo build
Compiling my-app v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.41s
real 0m2.48s
$ ls target/debug/
build/ deps/ examples/ incremental/ my-app my-app.d
So sánh với cargo check 0.85s, cargo build mất 2.41s — gấp khoảng 3 lần do thêm codegen + link.
Sau khi build xong, executable ở target/debug/my-app có thể chạy độc lập mà không cần Cargo:
$ ./target/debug/my-app
Hello, world!
Incremental compilation: lần build thứ 2 trở đi (không đổi code) gần như zero-cost — Cargo cache fingerprint của từng crate, chỉ recompile crate có thay đổi. Đổi 1 file trong crate chính: rebuild crate đó. Đổi file ở dependency: rebuild dependency đó + tất cả crate phụ thuộc (cascade).
$ time cargo build
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
real 0m0.09s
Khi nào dùng: muốn test binary thật, copy binary đi nơi khác, hoặc debug với debugger (lldb/gdb) cần symbol table đầy đủ — profile dev giữ nguyên debug info nên trải nghiệm debug rất mượt.
cargo run — Build + Execute Trong 1 Lệnh
cargo run thực hiện cargo build (nếu cần) rồi exec binary ngay. Lệnh phổ biến nhất khi dev: tiết kiệm 2 step thành 1.
$ cargo run
Compiling my-app v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.41s
Running `target/debug/my-app`
Hello, world!
Truyền argument cho binary: chú ý phải có -- để Cargo biết các flag sau đó thuộc về binary chứ không phải Cargo:
$ cargo run -- arg1 arg2
Running `target/debug/my-app arg1 arg2`
Got args: ["arg1", "arg2"]
# Sai: --port sẽ bị Cargo cố gắng parse và báo lỗi unknown flag
$ cargo run --port 8080
error: unexpected argument '--port' found
# Đúng: -- ngăn cách rõ ràng
$ cargo run -- --port 8080
Running `target/debug/my-app --port 8080`
Ưu điểm: dev loop ngắn — sửa code → cargo run → xem output → sửa tiếp. Không cần nhớ path target/debug/<name>.
Nhược điểm: mỗi lần chạy phải re-link (vài trăm ms ở project nhỏ, vài giây ở project lớn). Nếu chỉ muốn kiểm tra compile có pass không, dùng cargo check nhanh hơn nhiều.
cargo run cũng chấp nhận --release, --bin, --example, --features giống cargo build.
cargo build --release — Optimized Build
--release chuyển từ profile dev sang profile release: opt-level = 3, không debug info, có thể bật LTO. Binary nhỏ và nhanh hơn dev build từ 10 đến 50 lần tùy workload — nhưng build chậm hơn nhiều do LLVM phải tối ưu rất kỹ.
$ time cargo build --release
Compiling my-app v0.1.0
Finished `release` profile [optimized] target(s) in 18.92s
real 0m18.99s
$ ls target/release/
build/ deps/ examples/ incremental/ my-app my-app.d
Output ở target/release/<name> (khác target/debug/). Cargo giữ cả 2 thư mục song song để không phải rebuild khi switch profile.
$ ls -lh target/debug/my-app target/release/my-app
-rwxr-xr-x 4.2M target/debug/my-app
-rwxr-xr-x 412K target/release/my-app
Quy tắc: KHÔNG dùng release cho dev hằng ngày. Build 19s mỗi lần thay đổi sẽ phá nát workflow. Release chỉ phù hợp khi:
- Benchmark: đo perf phải dùng binary tối ưu, không thì số liệu vô nghĩa (dev có thể chậm gấp 50 lần).
- Deploy production: container, package binary cho user.
- Profile / flamegraph: cần code gần với production thực tế.
Pitfall: cargo run --release ở project lớn cũng phải build lại nếu thư mục target/release/ stale — chuẩn bị tinh thần chờ.
Flag --bin Cho Multiple Binary
Project có thể có nhiều binary: file src/main.rs là bin mặc định cùng tên package, và mỗi file trong src/bin/*.rs là một bin riêng (tên = tên file). Hoặc khai báo tường minh qua [[bin]] trong Cargo.toml (xem Bài 20).
$ tree src
src
├── main.rs # bin tên "my-app"
└── bin
├── server.rs # bin tên "server"
└── worker.rs # bin tên "worker"
Khi có nhiều bin, cargo run không biết chọn cái nào — phải chỉ định --bin:
# Build riêng bin "server"
$ cargo build --bin server
# Run bin "worker" và truyền arg cho nó
$ cargo run --bin worker -- --port 8080
Running `target/debug/worker --port 8080`
# Run bin "worker" ở release profile
$ cargo run --bin worker --release -- --port 8080
Running `target/release/worker --port 8080`
Idiom phổ biến trong project production: tách server (HTTP API), worker (background job), migrate (CLI chạy DB migration), cli (admin tool) thành nhiều bin chia sẻ chung library src/lib.rs. Cargo build chỉ những bin cần thiết khi specify --bin, tiết kiệm thời gian.
Flag --example Cho Example Programs
Thư mục examples/ dành cho chương trình demo — mỗi file .rs là một example độc lập, có fn main, dùng API public của crate để minh hoạ cách dùng. Khác bin: example KHÔNG vào binary chính khi cargo build mặc định, KHÔNG được publish lên crates.io binary, chỉ build khi gọi tên cụ thể.
$ tree examples
examples
├── basic.rs
├── smoke.rs
└── load_test.rs
# Build và chạy example "smoke"
$ cargo run --example smoke
Compiling my-app v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.87s
Running `target/debug/examples/smoke`
# Chỉ build, không chạy
$ cargo build --example smoke
# Build tất cả example
$ cargo build --examples
Use case: library crate (Axum, sqlx, tokio) đều có thư mục examples/ để người dùng tham khảo nhanh. cargo run --example basic nhân bản scenario chính chỉ trong vài giây, không cần tạo project test riêng.
Output binary nằm ở target/debug/examples/<name> (hoặc target/release/examples/<name> khi kèm --release).
Flag --features Bật Feature
Feature là cờ compile-time định nghĩa trong [features] của Cargo.toml (đã học ở Bài 20). Khi build, dùng --features để bật một hoặc nhiều feature:
# Bật 2 feature cùng lúc (cách nhau bằng dấu phẩy)
$ cargo build --features cache,redis
# Bật toàn bộ feature có thể (test compile mọi tổ hợp)
$ cargo build --all-features
# Tắt default feature, chỉ bật cái mình cần
$ cargo build --no-default-features --features json
# Run kèm feature
$ cargo run --features jwt -- --port 8080
--no-default-features đặc biệt hữu ích khi muốn binary nhỏ nhất có thể — tắt mọi feature mặc định mà mình không dùng, giảm code size đáng kể.
Pitfall: gõ sai tên feature, Cargo không báo lỗi ngay mà chỉ ghi warning nhỏ — chú ý đọc kỹ output dòng đầu tiên.
Combo thực dụng cho CI: chạy build matrix với --no-default-features, từng feature riêng, và --all-features để đảm bảo mọi #[cfg(feature = "x")] đều compile sạch.
Workflow Khuyến Nghị
Tổng hợp lại thành workflow hằng ngày:
- Khi đang gõ code:
cargo check(hoặc để rust-analyzer làm hộ trong IDE). Nhanh, biết ngay có lỗi compile không. - Khi cần test logic thực:
cargo run --bin foohoặccargo testđể chạy thử behavior. - Khi cần đo perf hoặc deploy:
cargo build --release. Đừng dùng dev binary để benchmark. - Khi có nhiều variant feature:
cargo build --no-default-features --features minimalđể kiểm tra binary lean.
Tip mạnh: cài cargo-watch để Cargo tự rerun mỗi lần file thay đổi — không phải gõ lệnh lại liên tục:
$ cargo install cargo-watch && cargo watch -x check -x test -x run
Lệnh trên sẽ: chạy cargo check trước (fail fast), sau đó cargo test, cuối cùng cargo run — mọi lúc bạn save một file .rs trong project. Có thể tinh chỉnh:
# Chỉ check liên tục (nhẹ nhất, dùng khi gõ refactor lớn)
$ cargo watch -x check
# Check + test, bỏ qua run
$ cargo watch -x check -x test
# Chạy server, kill và restart khi save
$ cargo watch -x 'run --bin server'
Kết hợp với rust-analyzer trong VS Code, dev loop Rust trở nên rất gần với JS/TS: save → thấy kết quả trong vài giây.
Tổng Kết
cargo check: typecheck + borrow check, KHÔNG link, KHÔNG sinh binary. Nhanh gấp 2-3 lần build. Lệnh mà rust-analyzer chạy ngầm liên tục.cargo build: compile + link, outputtarget/debug/<name>. Default profiledevvới debug info đầy đủ. Có incremental compilation.cargo run: gộp build (nếu cần) + execute. Dùng--để truyền argument cho binary tránh xung đột với flag Cargo.cargo build --release: profilerelease, opt-level 3, binary nhanh 10-50x nhưng build chậm hơn nhiều. Chỉ dùng cho bench/deploy, KHÔNG dev hằng ngày. Outputtarget/release/<name>.--bin <name>: chọn binary cụ thể khi project có nhiều bin trongsrc/bin/hoặc[[bin]].--example <name>: build và chạy file trongexamples/— demo cách dùng crate, không vào binary chính.--features a,bbật feature;--no-default-featurestắt default;--all-featuresbật hết để test compile matrix.- Workflow:
cargo checkkhi gõ →cargo runkhi test →cargo build --releasekhi bench/deploy. Càicargo-watchđể auto rerun on save.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Bạn vừa sửa 50 dòng code và muốn biết "có lỗi compile không?" mà chưa cần chạy thử. Lệnh nào tiết kiệm nhất, và nó bỏ qua phase nào của rustc?
- Đồng nghiệp than phiền
cargo run --port 8080báo lỗi "unexpected argument '--port'". Lệnh đúng phải là gì? Vì sao? - Project có
src/main.rs,src/bin/server.rs,src/bin/worker.rs. Gõcargo runtrần thì Cargo chọn binary nào? Nếu Cargo báo lỗi "could not determine which binary to run", phải chỉ định thế nào? - Bạn vừa benchmark binary
target/debug/my-appvà thấy throughput chỉ 1000 req/s. Đồng nghiệp nói "đo sai rồi". Vì sao? Sửa thế nào? - Crate có 3 feature
cache,jwt,metrics;default = ["jwt"]. Bạn muốn build với CHỈcache+metrics, không cójwt. Lệnh đầy đủ là gì?
Đáp án
cargo check— bỏ qua phase codegen (MIR → LLVM IR → object) và phase link. Chỉ chạy parse, name resolution, macro expansion, typecheck, borrow check. Nhanh gấp 2-3 lầncargo build.cargo run -- --port 8080. Dấu--ngăn cách flag của Cargo (trước nó) và argument của binary (sau nó). Không có--, Cargo cố parse--portnhư flag của chính nó và báo lỗi.- Cargo sẽ ưu tiên
src/main.rs(bin cùng tên package) khi có. Nếu xung đột hoặc package chỉ có lib + nhiều bin trongsrc/bin/, phải dùngcargo run --bin serverhoặccargo run --bin worker. - Vì binary
target/debug/là dev profile,opt-level = 0, không inline, không tối ưu. Số throughput thường thấp hơn release 10-50 lần và hoàn toàn không phản ánh thực tế. Sửa:cargo build --releaserồi chạytarget/release/my-app, hoặccargo run --release. cargo build --no-default-features --features cache,metrics.--no-default-featurestắtjwt(vì nó nằm trong default),--features cache,metricsbật 2 feature cần.
Bài Tiếp Theo
Bài 22: Profile dev vs release — Debug Build Và Optimized Build — đi sâu vào từng field của [profile.dev] và [profile.release]: opt-level, lto, codegen-units, strip, panic, custom profile [profile.bench] kế thừa release, và cách giảm binary size từ 30 MB xuống còn 5-8 MB.
