[Rust Book] 3.1. Biến và khả năng thay đổi

Từ khóa

Ngôn ngữ Rust có một bộ các từ khóa (keyword) được dành riêng và chỉ được sử dụng bới ngôn ngữ, giống như trong những ngôn ngữ khác. Hãy luôn nhớ rằng bạn không thể dùng những từ này cho tên biến hay tên hàm. Hầu hết các từ khóa có ý nghĩa đặc biệt, bạn sẽ dùng chúng để làm nhiều loại tác vụ trong chương trình Rust của bạn; một vài từ khóa hiện tại chưa có tính năng liên quan nhưng sẽ được sử dụng trong tương lai. Bạn có thể xem danh sách từ khóa ở Phụ lục A.

Biến và tính khả biến

Như đã đề cập trong “Storing Values with Variables”, mặc định biến là immutable (bất biến). Đây là một trong những thứ Rust cho bạn để bạn viết code an toàn và chạy đa luồng một cách dễ dàng. Tuy nhiên, bạn vẫn có tùy chọn để làm biến của bạn mutable (khả biến). Hãy cùng tìm hiểu cách thức và lý do Rust khuyến khích bạn chọn immutability và tại sao đôi khi bạn lại không muốn immutability.

Khi một biến là immutable, một khi một giá trị đã được gán vào một cái tên, bạn không thể thay đổi giá trị đó. Để minh họa cho điều này, hãy cùng tạo một project mới tên là variables trong thư mục projects của bạn bằng cách sử dụng cargo new variables.

Sau đó, trong thư mục variables, mở src/main.rs và thay thế code của nó với đoạn code sau (lưu ý là nó vẫn chưa chạy được):

fn main() {
    let x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

Lưu lại và chạy chương trình với cargo run. Bạn sẽ gặp lỗi như sau:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0384]: cannot assign twice to immutable variable `x`
 --> src/main.rs:4:5
  |
2 |     let x = 5;
  |         -
  |         |
  |         first assignment to `x`
  |         help: consider making this binding mutable: `mut x`
3 |     println!("The value of x is: {x}");
4 |     x = 6;
  |     ^^^^^ cannot assign twice to immutable variable

For more information about this error, try `rustc --explain E0384`.
error: could not compile `variables` due to previous error

Ví dụ này cho bạn thấy cách trình biên dịch giúp bạn tìm ra lỗi trong chương trình của bạn. Mặc dù lỗi biên dịch có thể trông khá khó chịu, nhưng nó chỉ có nghĩa rằng chương trình của bạn thực hiện một cách không an toàn những gì bạn muốn nó làm; nó không có nghĩa rằng bạn không phải là một lập trình viên giỏi! Những Rustacean có kinh nghiệm vẫn gặp những lỗi biên dịch thế này.

Tin nhắn lỗi chỉ ra rằng nguyên nhân của lỗi là cannot assign twice to immutable variable `x`, bởi vì bạn cố để gán giá trị thứ hai cho biến immutable x.

Việc nhận được những thông báo lỗi biên dịch này rất quan trọng khi chúng ta dự định thay đổi một giá trị mà trước đó chúng ta đã thiết kế nó là immutable bởi những tình huống như thế này rất có thể sẽ dẫn đến lỗi. Nếu một phần code của chúng ta thực thi theo giả định rằng một giá trị không bao giờ thay đổi còn một phần khác lại thay đổi giá trị đó, nó có khả năng là phần đầu tiên của code sẽ không làm như những gì nó đã được thiết kế. Nguyên nhân của loại lỗi này có thể khó lần ra, đặc biệt khi mẩu code thứ hai chỉ thỉnh thoảng mới thay đổi giá trị. Trong Rust, trình biên dịch đảm bảo rằng khi bạn nói rằng một giá trị không đổi thì nó sẽ không đổi. Điều đó có nghĩa rằng khi bạn đọc và viết code, bạn sẽ không phải quan tâm rằng giá trị có thể bị thay đổi ở đâu hay khi nào.

Nhưng mutability cũng rất hữu ích. Mặc định biến là immutable; như bạn đã làm trong Chương 2, bạn có thể làm nó mutable bằng cách thêm mut vào trước tên biến. Ngoài việc cho phép giá trị này thay đổi, mut cho người đọc biết những phần khác của code sẽ có thể thay đổi giá trị của biến này.

Ví dụ, hãy sửa src/main.rs thành như sau:

fn main() {
    let mut x = 5;
    println!("The value of x is: {x}");
    x = 6;
    println!("The value of x is: {x}");
}

Khi chạy chương trình, chúng ta nhận được output sau:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
     Running `target/debug/variables`
The value of x is: 5
The value of x is: 6

Khi mut được sử dụng, chúng ta được phép thay đổi giá trị của x từ 5 sang 6. Trong một vài trường hợp, bạn sẽ muốn làm cho biến mutable vì nó làm cho việc viết code tiện hơn so với việc chỉ toàn là biến immutable.

Có nhiều sự đánh đổi khác cần được xem xét ngoài việc ngăn chặn lỗi. Ví dụ, bạn đang sử dụng những cấu trúc dữ liệu lớn, việc cho phép một đối tượng có thể thay đổi sẽ nhanh hơn việc sao chép và trả về những đối tượng mới. Với những cấu trúc dữ liệu nhỏ hơn, tạo đối tượng mới và viết theo phong cách lập trình hướng chức năng có thể sẽ dễ hiểu hơn, nên hiệu năng thấp hơn một chút có thể chấp nhận được.

Hằng

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

Đầu tiên, bạn không được phép sử dụng mut với hằng. Hằng không chỉ mặc định là immutable mà chúng luôn immutable. Bạn khai báo hằng sử dụng từ khóa const thay vì từ khóa let, và kiểu giá trị phải được gán. Chúng ta sẽ nói về kiểu và gán kiểu trong phần tiếp, “Data Types,” nên bây giờ bạn không cần quan tâm quá chi tiết. Chỉ cần biết rằng bạn phải luôn gán kiểu.

Hằng có thể khai báo trong bất cứ vùng nào, kể cả toàn cục, trong trường hợp nhiều phần của code cần dùng nó.

Điểm khác biệt cuối cùng là hằng chỉ có thể được gán vào một constant expression, chứ không phải kết quả của một giá trị mà chỉ có thể tính toán được lúc runtime.

Đây là một ví dụ về khai báo hằng:

const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;

Tên hằng là THREE_HOURS_IN_SECONDS và giá trị của nó được gán bằng kết quả của phép nhân 60 (số giây một phút) với 60 (số phút một giờ) với 3 (số giờ chúng ta muốn đếm). Quy tắc đặt tên hằng trong Rust là tất cả các chữ đều viết hoa và phân cách bởi dấu gạch dưới giữa các từ. Trình biên dịch có thể tính toán một tập giới hạn các phép toán tại thời điểm biên dịch, cho phép chúng ta diễn giải giá trị này theo một cách dễ hiểu và dễ kiểm tra hơn là viết thẳng giá trị 10,800. Các bạn có thể tìm thông tin về những toán tử có thể sử dụng khi khai báo hằng tại Rust Reference’s section on constant evaluation.

Hằng có giá trị trong suốt thời gian chạy chương trình, trong scope nó đã được khai báo, khiến nó là lựa chọn hữu ích khi nhiều phần của chương trình cần sử dụng nó, ví dụ như số điểm tối đa người chơi có thể đạt được trong một game hay tốc độ của ánh sáng.

Dùng hằng thay cho các giá trị hardcoded xuyên suốt chương trình rất hữu ích trong việc truyền đạt nghĩa của chúng cho những người bảo trì code trong tương lai. Nó cũng giúp bạn chỉ cần cập nhật giá trị của chúng một lần ở một nơi thay vì phải cập nhật toàn bộ những giá trị hardcoded.

Shadowing

Như bạn đã thấy trong hướng dẫn làm trò chơi đoán số, ở phần “So sánh số đoán với số bí mật” trong Chương 2, bạn có thể khai báo một biến mới cùng tên với biến đã có. Các Rustacean nói rằng biến đầu tiên đã bị shadow bởi biến thứ hai, có nghĩa là khi biến được sử dụng, giá trị của biến thứ hai là thứ sẽ xuất hiện. Chúng ta có thể shadow một biến bằng cách sử dụng cùng tên biến và dùng từ khóa let như sau:

fn main() {
    let x = 5;

    let x = x + 1;

    {
        let x = x * 2;
        println!("The value of x in the inner scope is: {x}");
    }

    println!("The value of x is: {x}");
}

Chương trình này đầu tiên gán x bằng 5. Sau đó shadow x bằng cách lặp lại let x =, lấy giá trị ban đầu và cộng thêm 1 nên giá trị của x6. Lệnh let thứ 3 cũng che x, nhân giá trị trước đó với 2 để gán cho x giá trị cuối cùng là 12. Khi chúng ta chạy chương trình này, nó sẽ cho output như sau:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target/debug/variables`
The value of x in the inner scope is: 12
The value of x is: 6

Shadowing khác với việc đánh dấu một biến là mut, bởi vì chúng ta sẽ gặp lỗi biên dịch nếu chúng ta vô tình gán lại biến này mà không sử dụng let. Qua việc sử dụng let, chúng ta có thể thay đổi giá trị của biến mà vẫn có biến là immutable.

Điểm khác nữa giữa mut và shadow là bởi vì chúng ta tạo ra một biến mới với let, chúng ta có thể đổi kiểu của giá trị nhưng vẫn giữ được cùng tên. Ví dụ, chương trình của chúng ta hỏi một người dùng họ muốn bao nhiêu dấu cách giữa các đoạn chữ bằng cách bảo họ nhập vào những ký tự cách, nhưng sau đó chúng ta muốn lưu input đó như một số:

    let spaces = "   ";
    let spaces = spaces.len();

Cấu trúc này được phép vì biến space đầu tiên là một kiểu chuỗi và biến space mới thứ hai cùng tên là một kiểu số. Shadowing giúp chúng ta không mất công nghĩ ra những cái tên khác nhau, như space_strspace_num; thay vào đó, chúng ta có thể dùng lại cái tên space đơn giản. Tuy nhiên, nếu chúng ta cố dùng mut với trường hợp này, như đã chỉ ra, chúng ta sẽ nhận một lỗi biên dịch:

    let mut spaces = "   ";
    spaces = spaces.len();

Thông báo lỗi nói rằng chúng ta không được phóp thay đổi kiểu của biến:

$ cargo run
   Compiling variables v0.1.0 (file:///projects/variables)
error[E0308]: mismatched types
 --> src/main.rs:3:14
  |
2 |     let mut spaces = "   ";
  |                      ----- expected due to this value
3 |     spaces = spaces.len();
  |              ^^^^^^^^^^^^ expected `&str`, found `usize`

For more information about this error, try `rustc --explain E0308`.
error: could not compile `variables` due to previous error

Thế là chúng ta đã tìm hiểu biến làm việc như thế nào, hãy thử xem những kiểu dữ liệu khác mà chúng có thể có.

Source

https://doc.rust-lang.org/stable/book/ch03-01-variables-and-mutability.html

Bài liên quan

comments powered by Disqus