Rust là gì: Ngôn ngữ quản lý bộ nhớ an toàn không cần GC

Rust là một ngôn ngữ lập trình hệ thống nổi tiếng nhờ khả năng quản lý bộ nhớ an toàn mà không cần garbage collector. Bài viết này giải thích cơ chế ownership và borrow checker của Rust, đồng thời hướng dẫn cách bắt đầu dùng Cargo để build và quản lý dependency.

Rust khác biệt với C hay C++ ở điểm nó đẩy toàn bộ trách nhiệm giải phóng bộ nhớ về compile time. Thay vì để chương trình chạy rồi mới phát hiện lỗi use-after-free, trình biên dịch chặn lỗi ngay từ dòng code đầu tiên bạn gõ. Đây là lý do Rust được dùng rộng rãi trong những dự án yêu cầu cả hiệu năng cao lẫn độ ổn định tuyệt đối như nhân hạt Linux, hệ điều hành, và các dịch vụ backend xử lý hàng triệu request mỗi ngày.

Anh chup man hinh cargo clippy kiem tra ma Rust hello world trong terminal

Rust khác gì C và C++ về quản lý bộ nhớ

Trong C, bạn cấp phát bộ nhớ bằng malloc và giải phóng bằng free. Nếu quên một chỗ, chương trình vẫn chạy được cho tới khi tràn bộ nhớ. Nếu giải phóng hai lần, chương trình có thể segfault. Rust chấp dứt hoàn toàn mô hình đó bằng ba quy tắc được trình biên dịch kiểm tra tự động.

Quy tắc thứ nhất là mỗi giá trị có đúng một chủ sở hữu (owner). Quy tắc thứ hai là khi owner rời khỏi phạm vi, giá trị đó bị drop, thường là bằng cách gọi hàm hủy nếu bạn implement trait Drop. Quy tắc thứ ba là chỉ có một chủ sở hữu tại mỗi thời điểm, nên không thể tự do nhân bản hay xoá giá trị mà không thông báo cho trình biên dịch.

Rust phân biệt ba khái niệm liên quan tới nhau: move (chuyển quyền sở hữu), borrow bất biến (đọc nhiều lần, &T) và borrow khả biến (đọc ghi, &mut T). Tại một thời điểm, bạn có thể có nhiều tham chiếu bất biến cùng lúc, hoặc đúng một tham chiếu khả biến, nhưng không được trộn cả hai. Đây chính là luật giải quyết data race ở cấp ngôn ngữ thay vì ở cấp luồng.

Anh chup man hinh trinh bien dich rustc bien dich du an paru bang cargo

Ownership và borrow hoạt động thế nào

Xét đoạn code kinh điển sau đây, trong đó hàm nhận một chuỗi và in ra độ dài.

fn do_len(s: String) -> usize {
    s.len()
}

fn main() {
    let s = String::from("chu hung blog");
    do_len(s);
    println!("{}", s);
}

Đoạn này không biên dịch được. Khi gọi do_len(s), giá trị String bị move vào hàm, nghĩa là s ở main không còn hợp lệ. Trình biên dịch báo lỗi value borrowed here after move. Cách sửa đơn giản nhất là truyền tham chiếu bất biến:

fn do_len(s: &String) -> usize {
    s.len()
}

Lúc này quyền sở hữu vẫn nằm ở main, hàm chỉ mượn tạm thời rồi trả lại. Kỹ thuật này tạo ra zero-copy: không cần sao chép dữ liệu heap, chỉ truyền con trỏ.

Khi cần sửa dữ liệu, dùng &mut T. Ví dụ thêm phần tử vào vector sẽ bị từ chối nếu đang có một tham chiếu bất biến nào đó tồn tại, vì nếu vector nở ra reallocate thì tham chiếu cũ sẽ trỏ tới vùng nhớ đã bị giải phóng. Borrow checker bắt được đúng trường hợp nguy hiểm đó.

Zero-cost abstraction và static dispatch

Triết lý cốt lõi của Rust là zero-cost abstraction: cấu trúc ngôn ngữ phải cho code tốt nhưng không tốn thêm chi phí runtime. Iterator, closure, generic đều được trình biên dịch monomorphize thành code cụ thể cho từng kiểu, nên không có bảng tra cứu hay con trỏ hàm nào chạy lúc thực thi.

Khía cạnh Rust C++
Quản lý bộ nhớ Borrow checker tĩnh, không GC Con trỏ thô, không kiểm tra
Lỗi use-after-free Chặn lúc biên dịch Chỉ lộ khi chạy
Trait và interface Trait, static hoặc dynamic dispatch Class, virtual function
Song luồng Kiểu dữ liệu gửi được theo quyền sở hữu Khó hiểu, dễ race
Thời gian biên dịch Chậm hơn đáng kể Nhanh hơn

Đổi lại, Rust có trait object &dyn Trait dùng dynamic dispatch khi kiểu chưa biết lúc biên dịch. Con trỏ “mập” mang cả con trỏ dữ liệu lẫn con trỏ bảng phương thức, giúp viết code đa hình mà không cần biết trước mọi kiểu cụ thể.

Cargo và hệ sinh thái crate

Người mới bắt đầu Rust hầu như không cài trực tiếp trình biên dịch. Công cụ chuẩn Cargo đi kèm Rust lo hết việc build, kiểm thử, tài liệu và quản lý dependency. Một lệnh duy nhất tạo xong cả project:

cargo new vi_du_project
cd vi_du_project
cargo build
cargo test
cargo clippy

Thư viện trong Rust gọi là crate, được quản lý khai báo trong file Cargo.toml. cargo clippy chạy linter tĩnh phát hiện code có thể viết tốt hơn, ví dụ đề xuất dùng iter().cloned() thay vì iter().map(|x| x.clone()).

Doan ma nguon Rust hien thi tren man hinh may tinh

Rust phù hợp với dự án nào

Rust hợp với những hệ thống cần cả tốc độ lẫn an toàn: dịch vụ xử lý hiệu năng cao, phần mềm nhúng chạy trên thiết bị giới hạn tài nguyên, blockchain, công cụ CLI, và thư viện WebAssembly chạy trong trình duyệt. Cargo cho phép đóng gói ra binary tĩnh, chạy được trên hệ điều hành tối giản mà không cần cài runtime.

Rủi ro lớn nhất của Rust là đường cong học. Borrow checker buộc lập trình viên thay đổi tư duy từ “cứ cấp phát thoải mái” sang “suy nghĩ về vòng đời dữ liệu”. Bù lại, bạn không bao giờ phải chạy trình dò rò rỉ bộ nhớ hay công cụ kiểm tra luồng dữ liệu ở giai đoạn production.

Để bắt đầu, hãy đọc sách Rust chính thức, cài rustup trên máy, rồi làm hết các chương về ownership và struct trước khi chuyển sang async hay trait phức tạp. Một sai lầm phổ biến là học macro và generics quá sớm, khi nền tảng hệ thống borrow vẫn chưa vững.

Kết luận

Rust đánh đổi độ khó ban đầu để lấy sự an toàn ở tầng hệ thống mà không tốn hiệu năng. Ownership, borrow checker và trait cùng nhau tạo nên một ngôn ngữ mà lỗi bộ nhớ bị phát hiện trước khi chương trình chạy. Nếu dự án của bạn cần tốc độ gần C++, Cargo cùng hệ sinh thái crate sẽ là lựa chọn hợp lý hơn hẳn viết C++ thủ công.

Tôi là một lập trình viên IOS. Code chính là IOS nhưng thỉnnh thoảng vẫn đá sang Android hoặc web. Mặc dù không quá thông thạo nhưng tôi sẽ chia sẻ những kiến thức mà mình đã tìm hiểu, áp dụng qua.

Bài viết liên quan

Man hinh hien thi ma nguon JavaScript

Bun là gì: Runtime JavaScript nhanh tích hợp TypeScript

Bun là gì? Bun là một runtime JavaScript all-in-one được thiết kế để thay thế Node.js, nổi bật với tốc độ khởi động cực nhanh, tích hợp sẵn TypeScript, bundler…

Xem thêm

WebGPU là gì: Chuẩn đồ hoạ mới thay thế WebGL trên web

WebGPU là chuẩn API đồ hoạ thế hệ mới của web, cho phép JavaScript giao tiếp trực tiếp với GPU của thiết bị ở mức thấp, hiệu năng gần native….

Xem thêm

Go generics: Lập trình hàm generic thực tế từ A-Z

Go generics: Lập trình hàm generic thực tế từ A-Z Được ra mắt official trong Go 1.18 (-released February 2022), generics đã mang lại cáchปฏ cách khác cho lập trình…

Xem thêm
0 0 đánh giá
Article Rating
Theo dõi
Thông báo của
guest
0 Comments
Cũ nhất
Mới nhất Được bỏ phiếu nhiều nhất