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_str
và name_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 const
và phải có kiể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
All rights reserved