Mục lục
Mục Tiêu Bài Học
Sau bài học, bạn sẽ:
- Hiểu khái niệm Rust edition — cơ chế nâng cấp cú pháp 3 năm/lần mà không phá code cũ.
- Biết các highlight chính của Rust 2024 Edition: RPIT capture rule mới,
genblocks, never type fallback,unsafeblock trongunsafe fn,static_mut_refsdeny. - Đọc hiểu sự khác biệt code 2021 vs 2024 cho mỗi thay đổi.
- Chạy được lệnh
cargo fix --editionđể migrate workspace cũ sang 2024. - Quyết định được khi nào nên/chưa nên upgrade dự án production từ 2021 lên 2024.
Rust Edition Là Gì
Rust edition là cơ chế cho phép ngôn ngữ thay đổi cú pháp hoặc thêm keyword mới mà không phá code cũ. Mỗi crate khai báo edition của mình trong Cargo.toml:
[package]
name = "my-app"
version = "0.1.0"
edition = "2024"
rust-version = "1.85"
Crate edition 2015 và crate edition 2024 cùng tồn tại trong một workspace, link với nhau bình thường — khác biệt chỉ ở cú pháp và default semantic, không phải ABI.
Lịch sử: 2015 (Rust 1.0), 2018 (module gọn, async/await), 2021 (disjoint closure capture, panic format string), 2024 (stable cùng Rust 1.85, đầu 2025). Cadence ~3 năm. Edition không phải version compiler — Rust 1.85 chạy crate edition 2018 vẫn OK.
Highlights Của 2024
Edition 2024 không có "tính năng đột phá" như async/await ở 2018, mà gom nhiều cải thiện nhỏ tích lũy nhiều năm. Năm thay đổi đáng nhớ nhất:
- RPIT capture rule mới:
impl Traitreturn position tự capture mọi lifetime, không cần+ '_. - gen blocks:
genreserved làm syntax cho generator block, preview trên nightly. - never type fallback đổi sang
!: compiler không fallback về()nữa. unsafe fnphải cóunsafe {}bên trong: body không còn mặc nhiên là unsafe context.static_mut_refsdeny: lấy reference tớistatic mutgiờ là hard error.
Ngoài ra: cargo resolver v3 mặc định, if let temporary scope ngắn hơn, prelude thêm Future/IntoFuture. Các phần sau đi sâu từng cái.
RPIT Capture Tự Động Lifetime
RPIT (Return-Position Impl Trait) ở 2021 chỉ tự capture type parameter, không capture lifetime. Code dưới đây compile không được ở 2021:
// Edition 2021 — KHÔNG compile
fn first_word(s: &str) -> impl Iterator<Item = char> {
s.chars().take_while(|c| !c.is_whitespace())
}
// Error: hidden type captures lifetime that does not appear in bounds
Để fix ở 2021 phải workaround thêm + '_ hoặc named lifetime:
// Edition 2021 — fix bằng + '_
fn first_word(s: &str) -> impl Iterator<Item = char> + '_ {
s.chars().take_while(|c| !c.is_whitespace())
}
Edition 2024 đổi rule: RPIT tự động capture mọi lifetime in-scope. Code không có + '_ compile thẳng:
// Edition 2024 — compile OK, không cần + '_
fn first_word(s: &str) -> impl Iterator<Item = char> {
s.chars().take_while(|c| !c.is_whitespace())
}
Khi không muốn capture lifetime, dùng cú pháp mới use<'a, T> để khai báo rõ tập capture:
fn make_iter<'a>(s: &'a str) -> impl Iterator<Item = char> + use<> {
"static".chars()
}
Đây là thay đổi giảm friction nhất khi viết function trả iterator/closure — không còn phải nhớ + '_ mỗi lần.
gen Blocks — Generator Preview
Edition 2024 reserve từ khóa gen — không dùng làm tên biến/function nữa, dành cho syntax generator. Trên nightly đã có gen block sinh iterator giống Python/JS generator:
// nightly preview — đã hoạt động với feature gate
#![feature(gen_blocks)]
fn evens() -> impl Iterator<Item = u32> {
gen {
let mut n = 0u32;
loop {
yield n;
n += 2;
}
}
}
fn main() {
for x in evens().take(5) {
println!("{x}");
}
}
// 0 2 4 6 8
Trên stable 2024, gen chỉ là reserved keyword — viết let gen = 1; compile error. Mục tiêu: viết iterator stateful, nested loop, early return dễ hơn nhiều so với tự impl Iterator. Async version (async gen) sinh Stream cũng đang được thiết kế.
never Type Fallback Đổi
Type ! (never) là type của expression không bao giờ trả về — panic!(), return, loop {}. Khi không suy ra type concrete, compiler fallback. Trước 2024: về (); từ 2024: về !.
use std::error::Error;
fn parse_num(s: &str) -> Result<i32, Box<dyn Error>> {
let n = s.parse()?;
Ok(n)
}
fn main() -> Result<(), Box<dyn Error>> {
let r: Result<_, _> = (|| {
panic!("never returns");
})();
// 2021: r suy ra Result<(), ()>
// 2024: r suy ra Result<!, !>
Ok(())
}
! coerce được sang mọi type nên ít compile error hơn. Edge case code 2021 dựa vào fallback () bị break — cargo fix --edition tự thêm explicit annotation. Impact thực tế rất nhỏ, chủ yếu ở code macro phức tạp hoặc closure chứa panic! trực tiếp.
unsafe Block Trong unsafe fn + static_mut_refs Deny
Trước 2024, body unsafe fn mặc nhiên là unsafe context — gọi unsafe code thoải mái. Edition 2024 đảo: body unsafe fn giờ là safe context, mọi unsafe operation phải nằm trong unsafe {}. Lint unsafe_op_in_unsafe_fn deny mặc định:
// Edition 2024 — bắt buộc bao unsafe block
pub unsafe fn read_ptr<T>(ptr: *const T) -> T {
// KHÔNG được: *ptr (compile error)
unsafe { *ptr } // PHẢI có unsafe block
}
Ý nghĩa: ép chỉ rõ chỗ nào thực sự unsafe — unsafe ở signature chỉ nói "caller đảm bảo precondition", không nói body an toàn. Security audit dễ hơn nhiều.
Cùng tinh thần: static_mut_refs deny. Trước đây &mut COUNTER (với COUNTER: static mut u32) đầy data race tiềm tàng. Giờ compile error:
static mut COUNTER: u32 = 0;
fn bump() {
// Edition 2024 — error: creating a mutable reference to mutable static
unsafe {
let r = &mut COUNTER; // DENY
*r += 1;
}
}
Cách đúng: dùng AtomicU32 hoặc Mutex<u32> + OnceLock để init lười — toàn safe code, không cần static mut.
Migration: cargo fix --edition
Cargo có workflow chuẩn để migrate. Quy trình recommended:
# 1. Đảm bảo working tree sạch (commit hết)
git status
# 2. Cập nhật toolchain mới nhất
rustup update stable
# 3. Đang ở edition 2021 — auto-fix sang 2024 (chưa đổi Cargo.toml)
cargo fix --edition
# 4. Đổi edition trong Cargo.toml
# [package]
# edition = "2024"
# 5. Build lại để chắc OK
cargo build --all-targets
cargo test
cargo clippy --all-targets
# 6. Commit migration thành 1 commit riêng
git add -A && git commit -m "chore: migrate to Rust 2024 edition"
cargo fix --edition đọc warning compiler "code này sẽ break ở edition mới" và tự rewrite sang form tương thích cả 2 edition. Sau khi fix, code vẫn ở edition 2021 nhưng ready — chỉ cần đổi 1 dòng Cargo.toml.
Workspace nhiều crate: lệnh duyệt toàn bộ member. Có thể migrate từng crate độc lập (edition per-crate), PR nhỏ dễ review.
Checklist migration:
- Đổi
edition = "2024"trong mỗiCargo.tomlmember. - Thêm
rust-version = "1.85"để chặn compiler quá cũ. - Build kèm
--all-featuresđể bắt code chỉ enable bởi feature. - Chạy
cargo clippyvàcargo testđầy đủ. - Review thủ công
unsafe fnđể chắcunsafe {}đặt đúng chỗ.
Khi Nào Upgrade Từ 2021
- Dự án mới: dùng
edition = "2024"ngay —cargo newtrên Rust 1.85+ đã mặc định 2024. - Existing 2021 đang active: migrate sớm (Q2-Q3 2026), code mới hưởng RPIT capture rule và
unsaferigor. - Legacy ít sửa: không gấp — edition 2021 vẫn support dài hạn, không có EOL.
- Library publish: cân nhắc MSRV. Edition 2024 yêu cầu compiler 1.85+; nếu user lib còn compiler cũ, đợi 6-12 tháng cho ecosystem theo kịp.
Tổng Kết
- Rust edition = cơ chế nâng cấp cú pháp 3 năm/lần, không phá code cũ. Mỗi crate khai báo edition độc lập trong
Cargo.toml. - 2024 highlights: RPIT capture lifetime tự động,
genreserved keyword cho generator block, never type fallback đổi từ()sang!,unsafe fnbody phải cóunsafe {},static_mut_refsdeny mặc định. - RPIT mới: không cần thêm
+ '_nữa khi trả iterator/closure capture reference; dùnguse<>nếu muốn opt-out. unsafe fnbody giờ là safe context — ép audit chỉ rõ chỗ thực sự unsafe.- Migration:
cargo fix --edition+ đổiedition = "2024"+ chạycargo test. Per-crate, nên commit thành PR riêng. - Dự án mới dùng 2024 ngay; legacy upgrade theo schedule team — không gấp nhưng không nên trì hoãn quá lâu.
Bài Tập Củng Cố
Tự trả lời, đáp án ở cuối:
- Crate A của bạn dùng edition 2018, crate B (publish trên crates.io) dùng edition 2024. Crate A có dependency crate B. Có hoạt động được không? Vì sao?
- Hàm
fn iter_words(s: &str) -> impl Iterator<Item = &str> { s.split_whitespace() }ở edition 2021 compile không? Ở 2024 thì sao? Nếu khác, giải thích. - Bạn viết function
pub unsafe fn write_byte(ptr: *mut u8, val: u8) { *ptr = val; }. Compile ở edition 2024 ra gì? Sửa thế nào? - Một static mutable cũ
static mut LOGGER: Option<Logger> = None;dùng&mut LOGGERđể init. Edition 2024 báo lỗi. Đề xuất giải pháp idiomatic 2024. - Workspace có 8 crate, đang ở 2021. Bạn muốn migrate sang 2024. Nên migrate tất cả trong 1 PR hay từng crate? Trade-off mỗi cách.
- Function trả
impl Future<Output = u32>ở 2024 tự capture lifetime mọi¶m. Trường hợp nào bạn muốn opt-out, dùng cú pháp gì?
Đáp án
- Có hoạt động được. Edition là per-crate, không ảnh hưởng ABI — chỉ ảnh hưởng cách parse và một số default semantic. Crate A 2018 hoàn toàn có thể link với crate B 2024.
- Ở 2021 KHÔNG compile (hidden type captures lifetime
'_không có trong bounds). Phải thêm+ '_. Ở 2024 compile OK — RPIT tự capture lifetime'_của&str. - Compile lỗi với
unsafe_op_in_unsafe_fn. Sửa: bao trong unsafe block —pub unsafe fn write_byte(ptr: *mut u8, val: u8) { unsafe { *ptr = val; } }. Body của unsafe fn giờ là safe context. - Dùng
std::sync::OnceLock<Logger>— thread-safe, init lười, không cầnstatic mut:static LOGGER: OnceLock<Logger> = OnceLock::new();rồiLOGGER.get_or_init(|| Logger::new()). Toàn safe code. - Per-crate (8 PR nhỏ) tốt hơn cho review — diff dễ đọc, rollback dễ. Trade-off: tốn thời gian quản lý nhiều PR, vài tuần workspace ở trạng thái mixed edition (vẫn hoạt động). 1-PR-all phù hợp khi workspace nhỏ hoặc team có CI auto-test mạnh — risk merge conflict cao hơn.
- Khi
Futurekhông thực sự reference data từ param (vd: clone vào, hoặc dùng'staticdata). Cú pháp:impl Future<Output = u32> + use<>— opt-out tất cả lifetime in-scope, hoặcuse<'a>liệt kê rõ lifetime nào muốn capture.
Bài Tiếp Theo
Bài 312: Capstone 1 — CLI Tool Với clap (wc Clone) — mở Group 39 Capstone Projects. Build CLI tool wc-clone hoàn chỉnh: parse argument với clap derive, đọc file/stdin, đếm dòng/từ/byte/char, build release, cargo install --path.
