Biến, Kiểu Dữ Liệu Và Tính Bất Biến (Mutability) Trong Rust

lúc 17:47 30 tháng 8, 2026
24 views
Biến, Kiểu Dữ Liệu Và Tính Bất Biến (Mutability) Trong Rust

Tiếp nối phần cài đặt môi trường và làm quen với Cargo ở bài mở đầu, chúng ta sẽ bước vào nền tảng cốt lõi tiếp theo cực kỳ quan trọng của Rust: Cách lưu trữ dữ liệu trong bộ nhớ thông qua Biến và hệ thống Kiểu dữ liệu tĩnh.

🔒 1. Tính bất biến mặc định (Mutability) và từ khóa let

Trong phần lớn các ngôn ngữ lập trình phổ biến, khi bạn khai báo một biến, bạn có thể thoải mái thay đổi giá trị của nó bất cứ lúc nào trong vòng đời chương trình. Nhưng ở thế giới Rust, an toàn bộ nhớ và tính dễ đoán luôn được đặt lên hàng đầu.

  • Bất biến (Immutable): Khi bạn khai báo một biến bằng từ khóa let, giá trị của nó bị khóa chặt ngay sau khi được gán. Trình biên dịch sẽ lập tức từ chối biên dịch nếu bạn cố tình thay đổi giá trị của nó ở đâu đó trong code.
Code
fn main() {
    let x = 5;
    println!("The value of x is: {x}");

    // x = 6; // ❌ LỖI BIÊN DỊCH! Không thể gán lại giá trị cho biến bất biến
}
  • Biến thay đổi được (Mutable): Nếu bài toán thực tế yêu cầu bạn phải thay đổi giá trị của biến trong quá trình chương trình thực thi, bạn bắt buộc phải chủ động khai báo tường minh bằng cách thêm từ khóa mut (mutable) ngay sau let:
Code
fn main() {
    let mut x = 5;
    println!("Initial value: {x}");

    x = 6; // ✅ HOÀN TOÀN HỢP LỆ!
    println!("Value after change: {x}");
}

Góc nhìn thực chiến: Tính bất biến mặc định giúp mã nguồn của bạn trở nên minh bạch và dễ kiểm soát hơn rất nhiều, đặc biệt khi làm việc trong môi trường đa luồng (multi-threading). Khi biết chắc chắn một biến không bị thay đổi ngầm ở đâu đó, lập trình viên và trình biên dịch có thể tối ưu hóa hiệu năng và ngăn chặn triệt để các lỗi logic khó phát hiện.

👻 2. Hiện tượng Shadowing (Che khuất biến)

Khác với khái niệm gán lại giá trị cho biến thay đổi được (mut), Rust cho phép bạn khai báo một biến mới trùng tên với biến cũ bằng cách sử dụng lại từ khóa let. Hiện tượng này được gọi là Shadowing.

Code
fn main() {
    let x = 5;

    let x = x + 1; // Biến x mới "che khuất" biến x cũ, giá trị lúc này là 6

    {
        let x = x * 2; // Biến x trong block scope này có giá trị 12
        println!("Value of x in inner scope: {x}");
    }

    println!("Value of x in outer scope: {x}"); // In ra 6
}

Ứng dụng thực tế: Shadowing cực kỳ hữu ích khi bạn muốn biến đổi kiểu dữ liệu của biến (ví dụ: chuyển từ chuỗi String sang số nguyên u32) hoặc thực hiện một vài phép biến đổi phức tạp nhưng vẫn đảm bảo tính chất bất biến của biến sau khi quá trình xử lý hoàn tất.

📊 3. Các kiểu dữ liệu nguyên thủy (Primitive Types)

Rust là ngôn ngữ statically typed (kiểm tra kiểu dữ liệu chặt chẽ tại thời điểm biên dịch). Trình biên dịch thường có khả năng tự suy luận ra kiểu dữ liệu dựa vào giá trị bạn gán, nhưng bạn hoàn toàn có thể tự định nghĩa tường minh khi cần thiết.

A. Kiểu số nguyên (Integers)

Số nguyên là các con số không có phần thập phân. Rust cung cấp các biến thể có dấu (signed - chứa cả số âm và dương) và không dấu (unsigned - chỉ chứa số dương) với đa dạng độ dài bit:

Chiều dàiCó dấu (Signed)Không dấu (Unsigned)8-biti8u816-biti16u1632-biti32 (Mặc định tối ưu)u3264-biti64u64128-biti128u128Archisizeusize (Dùng cho kích thước bộ nhớ/index mảng)

Mẹo tối ưu: Nếu bạn chưa biết nên chọn kiểu số nguyên nào cho bài toán của mình, hãy ưu tiên dùng i32 vì đây là kiểu mặc định và có hiệu năng xử lý cực tốt trên hầu hết các kiến trúc CPU hiện đại ngày nay.

B. Kiểu số thực (Floating-Point Types)

Rust cung cấp hai kiểu số thực cơ bản biểu diễn các con số có phần thập phân:

  • f32: Số thực độ chính xác đơn (32-bit).
  • f64: Số thực độ chính xác kép (64-bit - đây là kiểu mặc định vì tốc độ xử lý trên các CPU hiện đại ngang ngửa f32 nhưng độ chính xác và dung sai thấp hơn nhiều).
Code
fn main() {
    let _x = 2.0; // Mặc định là f64
    let y: f32 = 3.0; // Định nghĩa tường minh kiểu f32
}

C. Kiểu Boolean và Ký tự (Char)

  • Boolean (bool): Chỉ nhận một trong hai giá trị duy nhất là true hoặc false (chiếm 1 byte trong bộ nhớ).
  • Character (char): Đại diện cho một ký tự đơn. Khác với nhiều ngôn ngữ khác (chỉ lưu 1 byte cho mã ASCII), kiểu char trong Rust rộng 4 bytes (32-bit), hỗ trợ toàn bộ chuẩn Unicode mở rộng (bao gồm cả chữ cái có dấu tiếng Việt, ký tự tiếng nước ngoài và các biểu tượng cảm xúc như 🦀).
Code
fn main() {
    let _letter = 'Z';
    let _rust_emoji = '🦀'; // Hoàn toàn hợp lệ nhờ hỗ trợ chuẩn Unicode!
}

🧩 4. Kiểu dữ liệu hỗn hợp (Compound Types)

Compound types cho phép bạn gom nhóm nhiều giá trị có thể khác nhau hoặc giống nhau vào chung một biến duy nhất. Hai kiểu phổ biến nhất được tích hợp sẵn trong ngôn ngữ là Tuple và Array.

A. Tuple

Tuple giúp tập hợp một chuỗi các giá trị có kiểu dữ liệu khác nhau lại với nhau thành một thực thể duy nhất. Tuple có kích thước cố định: một khi đã được tạo ra, bạn không thể thêm hoặc bớt số lượng phần tử của nó.

Code
fn main() {
    // Khai báo một tuple chứa các kiểu dữ liệu khác nhau
    let user_info: (i32, f64, u8) = (500, 6.4, 1);

    // Truy xuất phần tử thông qua Destructuring (giải cấu trúc)
    let (age, height, _id) = user_info;
    println!("Age: {age}, Height: {height}");

    // Hoặc truy xuất trực tiếp từng phần tử thông qua dấu chấm (.) kết hợp vị trí index
    let first_element = user_info.0;
    println!("First element: {first_element}");
}

B. Array (Mảng tĩnh)

Array khác với Tuple ở một điểm cốt lõi: Tất cả các phần tử có trong một Array bắt buộc phải có cùng một kiểu dữ liệu đồng nhất. Array trong Rust được lưu trữ hoàn toàn trên Stack thay vì Heap, mang lại tốc độ truy xuất cực nhanh nhưng có kích thước cố định từ lúc biên dịch.

Code
fn main() {
    // Khai báo một mảng chứa 5 phần tử kiểu i32
    let numbers: [i32; 5] = [1, 2, 3, 4, 5];

    // Lấy phần tử đầu tiên bằng index
    let first_number = numbers[0];
    println!("First number in array: {first_number}");
}

Lưu ý bảo mật quan trọng: Nếu bạn cố tình truy cập một mảng vượt quá giới hạn cho phép, ví dụ gọi numbers[10], Rust sẽ không cho phép chương trình tiếp tục chạy mà sẽ kích hoạt cơ chế panic ngay tại thời điểm thực thi để chặn đứng lỗi tràn bộ nhớ nguy hiểm.

Thông qua bài viết này, bạn đã nắm vững tư duy quản lý dữ liệu đặc thù của Rust thông qua tính bất biến (let so với let mut), hiểu rõ cách phân loại và sử dụng các kiểu dữ liệu nguyên thủy, cũng như cách tổ chức tập hợp dữ liệu bằng Tuple và Array.

Hẹn gặp lại bạn ở bài viết tiếp theo với chủ đề Hàm, Biểu thức và Luồng điều khiển!

Bình luận

Đăng nhập để để lại bình luận.
Chưa có bình luận nào cho bài viết này.

Bài viết liên quan