+2

Variables và Mutability trong Rust

Mở đầu

Chào mừng các bạn trở lại với chuỗi bài viết về Rust! Ở bài trước, chúng ta đã thiết lập môi trường cho Rust. Hôm nay, chúng ta sẽ cùng nhau tìm hiểu về biến (Variables), hằng (Constants) trong Rust và xem có điểm gì đặc biệt so với các ngôn ngữ lập trình khác không nhé.

Variables

Cũng tương tự như các ngôn ngữ khác, Variables trong Rust cũng giống như chiếc hộp, giúp bạn có thể đặt nhiều giá trị trong nó từ số, chữ cái và nhiều kiểu dữ liệu phức tạp khác. Tuy nhiên, Rust cũng có một số quy tắc về các sử dụng "chiếc hộp" này.

Mặc định là Immutable

Để khởi tạo một biến trong Rust, chúng ta sẽ dùng từ khóa let, mặc định khi tạo biến đó là immutable (không thay đổi), tức sau khi khởi tạo biến, bạn không thể thay đổi giá trị của nó nữa.

fn main() {
    let name = "Quan";
    println!("My name is: {name}");
    name = "Quan Troy";
    println!("My name is: {name}");
}

// Ouput: Error message "cannot assign twice to immutable variable `name`"
// Bởi vì bạn cố gán giá trị cho `name` trong khi nó là Immutable

Trình biên dịch Rust đảm bảo rằng nếu bạn khai báo một giá trị là immutable (không thay đổi), nó thực sự sẽ không thay đổi, giúp bạn dễ dàng quản lý code hơn. Tránh cho việc khi chạy chương trình có một đoạn code nào đó làm thay đổi giá trị của biến mà bạn không mong muốn.

Tuy nhiên, bạn vẫn có option để khai báo biến là có thể thay đổi (mutable) bằng cách thêm mut vào trước tên biến. Việc thêm mut cũng giúp người đọc code biết biến này sẽ được thay đổi giá trị trong tương lai. Chúng ta thử sửa lại ví dụ bên trên một chút:

fn main() {
    let mut name = "Quan";
    println!("My name is: {name}");
    name = "Quan Troy";
    println!("My name is: {name}");
}
//Output
% cargo run      
   Compiling hello-world v0.1.0 (/<PATH>/hello-world)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.18s
     Running `target/debug/hello-world`
My name is: Quan
My name is: Quan Troy

Ở đây, bạn đã cho phép biến name có thể thay đổi khi thêm mut. Vậy khi nào thêm mut khi nào không? Điều này bắt buộc bạn phải hiểu rõ code của bạn đang làm gì để dùng immutable hay mutability cho hợp lý. Tránh việc khai báo lung tung trong khi bạn không hề muốn thay đổi nó một chút nào. Đến đây, chắc hẳn bạn nghĩ biến immutable giống với hằng (constants) quá, không biết có khác gì không nhỉ? Chúng ta sẽ tìm hiểu ở phần tiếp theo nhé.

Shadowing

Rust cho phép bạn "biến hình" variables. Bạn có thể tạo ra một biến mới cùng tên, nhưng có thể khác kiểu hoặc giá trị. Điều đó có nghĩa biến thứ hai sẽ làm "lu mờ" biến thứ nhất, khi bạn sử dụng thì trình biên dịch sẽ chỉ thấy biến thứ hai.

fn main() {
    let name = "Quan";
    let name = "Quan Troy";
    println!("My name is: {name}");
}
//Ouput: My name is Quan Troy

Để kiểm tra nó có phải 2 biến khác nhau không, bạn có thêm thêm đoạn code println!("Address of name: {:p}", &name); bên dưới dòng khai báo biến lần một và hai để nó in ra địa chỉ ô nhớ của name khai báo lần thứ nhất và lần hai nhé.

Trông ví dụ trên có vẻ khá giống với một biến là mut nhỉ. Tuy nhiên, nó hoàn toàn khác nhau, bởi khi dùng let để Shadowing thì biến đó sau khi được gán thì biến đó vẫn là immutable (không thay đổi). Một điểm nữa là Shadowing giúp bạn tạo biến mới cùng tên nhưng khác kiểu dữ liệu, điều mà mut không thể làm được.

let name = "Quan Troy";
let name = name.len();
// Không vấn đề gì

let name = "Quan Troy";
name = name.len();
// Lỗi không cho phép mutate thay đổi kiểu dữ liệu

Việc sử dụng Shadowing giúp bạn tránh phải đặt quá nhiều tên biến ví dụ name_strname_num để lưu các giá trị khác nhau mà chỉ cần name là đủ rồi.

Kiểu dữ liệu

Rust rất thông minh, nó thường có thể đoán được kiểu dữ liệu của biến. Nhưng đôi khi bạn cần phải "nói" cho Rust biết:

let secret_number: u32 = 42; // u32 là số nguyên không âm 32-bit

Scope

Variables trong Rust sống trong những "ngôi nhà" gọi là scope được định nghĩa trong "{}". Khi ra khỏi scope, chúng sẽ bị xóa đi để giải phóng bộ nhớ, phần này chúng ta sẽ tìm hiểu kĩ hơn ở phần ownership và borrowing.

{
    let ghost = "Quan!";
} // ghost biến mất ở đây!

Constants

Giống như các biến immutable, hằng là các giá trị được gắn với một tên và không được phép thay đổi, nhưng cũng có một số khác biệt giữa hằng và biến.

Constants không thể dùng mut, chúng không chỉ mặc định là immutable mà luôn luôn là immutable. Khi khai báo dùng từ khóa constphảikiểu dữ liệu. Quy ước đặt tên của Rust cho các hằng số là sử dụng tất cả chữ hoa và dấu gạch dưới giữa các từ.

fn main() {
    const MAX_POINT: u32 = 100_000; //
    println!("Max point: {}", MAX_POINT);
}
//Output: Max point: 100000

Scope

Hằng có thể được khai báo ở phạm vi toàn cục hoặc cục bộ. Không thể khai báo cùng một const 2 lần trong cùng Scope, nhưng nếu khác Scope thì được.

const MAX_POINT: u32 = 100; //global scope
//const MAX_POINT: u32 = 1000; -> ERROR
fn main() {
    const MAX_POINT: u32 = 100_000; // OK -> chỉ có scope trong "{}" của hàm main
    println!("Max point: {}", MAX_POINT);
}
//Output: Max point: 100000

Tổng kết

Chúng ta đã đi qua một số khái niệm cơ bản về biến trong Rust: từ sự khác biệt giữa immutable và mutable, đến các hằng (constants) và shadowing. Việc quản lý biến trong Rust không chỉ giúp code an toàn hơn mà còn tăng tính rõ ràng và hiệu quả. Hy vọng rằng bài viết này sẽ giúp bạn tự tin hơn khi làm việc với Rust. Nếu có thắc mắc gì, đừng ngại comment ở dưới nhé! Happy Rustaceans!

Cảm ơn bạn đã dành thời gian đọc bài viết.

Tài Liệu Tham Khảo

https://doc.rust-lang.org/book/


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí