Danh sách bài viết

Bài 311: Rust 2024 Edition — Gì Mới

Bài 311 của series Rust Cơ Bản — Rust 2024 Edition ổn định cùng Rust 1.85 (đầu 2025) là lần nâng cấp edition lớn nhất kể từ 2018, mang theo hàng loạt thay đổi nhỏ-nhưng-quan-trọng: RPIT (return-position impl Trait) tự động capture lifetime, từ khóa gen reserved cho generator block preview, never type (!) fallback đổi từ () sang !, unsafe fn bắt buộc bao body bằng unsafe {}, lint static_mut_refs deny mặc định, và nhiều rule lifetime capture mới. Bài này tổng hợp các highlight quan trọng nhất cho người viết Rust hằng ngày, hướng dẫn migration bằng cargo fix --edition, và đưa checklist khi nào nên upgrade từ 2021 lên 2024.

11/06/2026
10 phút đọc
0 lượt xem
1

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, gen blocks, never type fallback, unsafe block trong unsafe fn, static_mut_refs deny.
  • Đọ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.
2

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ớikhô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.

3

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:

  1. RPIT capture rule mới: impl Trait return position tự capture mọi lifetime, không cần + '_.
  2. gen blocks: gen reserved làm syntax cho generator block, preview trên nightly.
  3. never type fallback đổi sang !: compiler không fallback về () nữa.
  4. unsafe fn phải có unsafe {} bên trong: body không còn mặc nhiên là unsafe context.
  5. static_mut_refs deny: lấy reference tới static mut giờ 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.

4

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.

5

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

6

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.

7

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.

8

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ỗi Cargo.toml member.
  • 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 clippycargo test đầy đủ.
  • Review thủ công unsafe fn để chắc unsafe {} đặt đúng chỗ.
9

Khi Nào Upgrade Từ 2021

  • Dự án mới: dùng edition = "2024" ngay — cargo new trê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à unsafe rigor.
  • 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.
10

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, gen reserved keyword cho generator block, never type fallback đổi từ () sang !, unsafe fn body phải có unsafe {}, static_mut_refs deny mặc định.
  • RPIT mới: không cần thêm + '_ nữa khi trả iterator/closure capture reference; dùng use<> nếu muốn opt-out.
  • unsafe fn body giờ là safe context — ép audit chỉ rõ chỗ thực sự unsafe.
  • Migration: cargo fix --edition + đổi edition = "2024" + chạy cargo 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.
11

Bài Tập Củng Cố

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

  1. 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?
  2. 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.
  3. 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?
  4. 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.
  5. 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.
  6. Function trả impl Future<Output = u32> ở 2024 tự capture lifetime mọi & param. Trường hợp nào bạn muốn opt-out, dùng cú pháp gì?
Đáp án
  1. 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.
  2. Ở 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.
  3. 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.
  4. Dùng std::sync::OnceLock<Logger> — thread-safe, init lười, không cần static mut: static LOGGER: OnceLock<Logger> = OnceLock::new(); rồi LOGGER.get_or_init(|| Logger::new()). Toàn safe code.
  5. 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.
  6. Khi Future không thực sự reference data từ param (vd: clone vào, hoặc dùng 'static data). Cú pháp: impl Future<Output = u32> + use<> — opt-out tất cả lifetime in-scope, hoặc use<'a> liệt kê rõ lifetime nào muốn capture.
12

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.