Rust – Các thành phần của Rust – Phần 2


Được đăng vào ngày 30/09/2017 | 0 bình luận
Đánh giá bài viết

Trong phần này chúng ta tiếp tục tìm hiểu các thành phần cấu tạo nên Rust.

Biến

Trong phần trước chúng ta đã tìm hiểu về hằng số, giá trị được gán cho hằng số là không thể thay đổi được, để có thể thay đổi được giá trị thì chúng ta gán cho các biến, để khai báo một biến thì chúng ta dùng từ khóa let, tiếp theo là dấu bằng =, rồi đến giá trị và dấu chấm phẩy, ví dụ:

fn main() {
let name = "phocode.com"; // Gán chuỗi "phocode.com" cho biến name
println!("Blog URL: {}", name);
}

Đoạn code trên sẽ không báo lỗi nhưng Rust sẽ in câu cảnh báo biến name được khai báo nhưng không được sử dụng (nên sẽ gây lãng phí).

warning: unused variable: `name`

Chúng ta có thể loại bỏ câu thông báo bằng cách đặt tên biến có dấu gạch ngang đầu tiên, Rust sẽ hiểu là biến này được khai báo nhưng có thể không được sử dụng, ví dụ:

let _name = 5;

Khác với khai báo hằng, đối với khai báo biến thì chúng ta không cần chỉ ra kiểu dữ liệu, thay vào đó Rust sẽ nhìn vào giá trị và tự gấn kiểu dữ liệu phù hợp cho biến, chẳng hạn như ở trên Rust sẽ cho biến name kiểu dữ liệu là &str.

Giá trị bất biến

Giá trị bất biến là các giá trị không thể thay đổi được. Tức là đoạn code sau sẽ báo lỗi:

fn main() {
let num = 1;
num = 2017; // Báo lỗi
}

Khi chúng ta khai báo num có giá trị 1, biến trong Rust có giá trị luôn luôn bất biến, chúng ta không thể thay đổi được giá trị.

error[E0384]: re-assignment of immutable variable `num`

Nếu chúng ta muốn biến có thể thay đổi giá trị được thì chúng ta phải khai báo thêm từ khóa mut phía trước tên biến như sau:

fn main() {
let mut num = 1;
num = 2017; // OK
}

Một điều nữa là tuy chúng ta không cần chỉ định kiểu dữ liệu khi khai báo biến vì Rust có thể tự nội suy kiểu, nhưng chúng ta cũng nên khai báo kiểu, chẳng hạn như trong trường hợp chúng ta chỉ khai báo tên biến chứ không gán dữ liệu thì Rust không thể nào biết được biến này nên dùng kiểu dữ liệu gì, ví dụ:

fn main() {
let num1; // Lỗi error[E0282]: type annotations needed
let num2: i32; // OK
let num3: i32 = 2017; // OK
}

Vậy cú pháp đầy đủ để khai báo và gán giá trị cho biến là:

let <tên biến>: <tên kiểu dữ liệu> = <giá trị>;

Phạm vi hoạt động của biến

Tất cả các biến đều có phạm vi hoạt động, phạm vi hoạt động có thể hiểu là khu vực mà chúng ta có thể thao tác với các biến, thông thường là nằm bên trong một cặp dấu {...}, chẳng hạn như với ví dụ phía trên là các biến num1, num2num3 có phạm vi hoạt động trong hàm main(). Ở ngoài dấu {} thì các biến này không tồn tại.

Chúng ta cũng có thể khai báo các cặp dấu {...} lồng nhau, ví dụ:

fn main() {    
let num1: i32 = 2000;   
         
{
let num2: i32 = 17;
println!("num1 + num2 = {}", num1 + num2);
}
}

Trong đoạn code trên chúng ta có phạm vi của hàm main(), trong này khai báo biến num1, sau đó chúng ta lồng thêm một cặp {} nữa, bên trong cặp này chúng ta khai báo biến num2 và in ra giá trị của num1 + num2.

num1 + num2 = 2017

Biến num2 chỉ tồn tại bên trong cặp dấu {} được lồng bên trong hàm main(), tức là nếu chúng ta sử dụng biến num2 như sau thì sẽ bị lỗi:

fn main() {    
let num1: i32 = 2000;             
{
let num2: i32 = 17;
}
println!("num1 + num2 = {}", num1 + num2);
}

Trong đoạn code trên chúng ta sử dụng num2 ở hàm main() nhưng num2 không được khai báo trong hàm main() nên biến này không tồn tại và do đó Rust sẽ báo lỗi.

error[E0425]: cannot find value `num2` in this scope

Typesafe

Rust là một ngôn ngữ typesafe, nói đơn giản thì biến nào đã được quy định kiểu dữ liệu nào thì luôn luôn phải được gán giá trị có kiểu dữ liệu đó, không thể thay đổi được (khác với các ngôn ngữ thông dịch như Python, Javascript…). Đoạn code sau sẽ báo lỗi:

fn main() {    
let num1: i32 = 10;
num1 = "phocode.com"; // Lỗi error[E0308]: mismatched types
}

Tuy nhiên Rust lại cho phép chúng ta tái định nghĩa biến (trùng tên) bằng từ khóa let, và biến trùng tên đã được định nghĩa trước đó sẽ được giải phóng khỏi bộ nhớ, ví dụ:

fn main() {    
let num1: i32 = 10;
let num1 = "phocode.com";
}

Có một điểm lưu ý là trong Rust thì toán tử + không thể dùng được với kiểu str để nối chuỗi, đoạn code sau sẽ báo lỗi:

fn main() {    
let str1 = "phocode";
let str2 = ".com";
let str3 = str1 + str2;
println!("{}", str3);
}

error[E0369]: binary operation `+` cannot be applied to type `&str`

Tuy nhiên Rust lại cho chúng ta một hàm có tên to_string() để chuyển sang một kiểu dữ liệu khác là String (khác với str) và chúng ta lại có thể thực hiện nối chuỗi với kiểu này:

fn main() {
let str1 = "phocode";
let str2 = ".com";
let str3 = str1.to_string() + str2;
println!("{}", str3);
}

phocode.com

Ép kiểu

Giả sử chúng ta có đoạn code sau:

fn main() {
let _num1: i32 = 10;
let mut _num2: u32 = 0;
_num2 = _num1; // Lỗi error[E0308]: mismatched types
}

Chúng ta định nghĩa 2 biến _num1 có kiểu i32_num2 có kiểu u32, cả 2 kiểu này đều là kiểu số nguyên (chúng ta sẽ tìm hiểu về kiểu dữ liệu sau), tuy nhiên chúng khác nhau về mặt độ lớn, do đó chúng ta cũng không thể nào gán giá trị của 2 biến này cho nhau được.

Rust cho phép chúng ta ép kiểu đối với các kiểu dữ liệu tương đồng với nhau bằng cách dùng từ khóa as, ví dụ:

fn main() {
let _num1: i32 = 10;
let mut _num2: u32 = 0;
_num2 = _num1 as u32;
}

Ở đây Rust sẽ chuyển đổi kiểu dữ liệu của giá trị trong biến _num1 thành u32 trước rồi mới gán vào biến _num2.

Chỉ có các kiểu dữ liệu tương đồng với nhau mới có thể ép kiểu được chẳng hạn như i32u32, còn những kiểu không giống nhau như i32str thì không thể nào “ép” được.

Alias

Alias là tính năng cho phép chúng ta đặt tên mới cho các kiểu dữ liệu, bởi vì đôi khi tên các kiểu dữ liệu trong Rust khá khó hiểu, để đặt tên alias thì chúng ta khai báo với từ khóa type, ví dụ:

type PhoCodeInteger = i32;

fn main() {
let _n: PhoCodeInteger = 9999;
}

Trong đoạn code trên chúng ta định nghĩa một tên mới cho kiểu i32PhoCodeInteger.

Được đăng vào ngày 30/09/2017