Daily Archives: 31/12/2017

Rust – Borrowing và Ownership

Mỗi ứng dụng được viết, cho dù là làm gì đi nữa, từ đọc dữ liệu database, hay thực hiện các công việc tính toán phức tạp… thì cũng đều là làm việc với một nguồn tài nguyên nào đó. Nguồn tài nguyên mà chúng ta thường thấy đối với một ứng dụng là vùng bộ nhớ trong RAM chứa tất cả các biến có trong ứng dụng đó. Các nguồn tài nguyên khác có thể có là các file, các kết nối mạng, kết nối database…

Trong Rust thì mỗi một tài nguyên đều được đặt một cái tên, đó là khi chúng ta thực hiện câu lệnh let, và khi đó tài nguyên đó được gọi là có chủ (hay có owner trong tiếng Anh). Ví dụ:

struct Blog {
year: i32,
name: &’static str
}
fn main() {
let phocode: Blog = Blog{
year: 2017,
name: "phocode.com"
};
}

Trong đoạn code trên, biến phocode sở hữu một vùng nhớ trong RAM có độ lớn bằng độ lớn của 1 biến i32 cộng với một biến str.

Và chỉ có duy nhất biến phocode được quyền thay đổi giá trị của vùng bộ nhớ đó thôi, và tại một thời điểm thì chỉ có duy nhất một biến được quyền sở hữu một vùng bộ nhớ đó thôi. Lúc này biến phocode là chủ của vùng bộ nhớ đó.

Một vùng bộ nhớ có thể được thay đổi chủ khi chúng ta dùng câu lệnh let như sau:

let phocode2 = phocode;

Lúc này thì chủ mới của vùng bộ nhớ là phocode2 chứ không phải phocode nữa. Tuy nhiên các giá trị bên trong vùng bộ nhớ đó thì vẫn giữ nguyên, không thay đổi.

Lúc này nếu chúng ta cố gắng truy xuất giá trị từ phocode hay thay đổi giá trị của phocode2 thì trình biên dịch sẽ báo lỗi:

struct Blog {
year: i32,
name: &’static str
}

fn main() {
let phocode: Blog = Blog{
year: 2017,
name: "phocode.com"
};
let phocode2 = phocode;
phocode2.year = 2016; // lỗi: cannot assign to immutable field `phocode2.year`
println!("{}", phocode.name); // lỗi: use of moved value `phocode.com`
}

Lý do là vì vùng bộ nhớ này là không thể thay đổi giá trị được (mặc định trong Rust). Và bởi vì phocode không còn là chủ nhân của vùng tài nguyên đó nữa nên không có quyền đọc giá trị trong vùng tài nguyên này.

Tuy nhiên, khi chúng ta gán tên biến mới theo kiểu tham chiếu đã học trong bài trước như sau:

let phocode2 = &mut phocode;

Thì lúc này biến phocode2 đang mượn tài nguyên của ông chủ là biến phocode.  Lúc này chúng ta có thể đọc/ghi tài nguyên của phocode thông qua biến phocode2 như bình thường, nhưng nếu chúng ta thao tác thông qua phocode thì Rust lại báo lỗi, ví dụ:

struct Blog {
year: i32,
name: &’static str
}
fn main() {
let mut phocode: Blog = Blog{
year: 2017,
name: "phocode.com"
};
let phocode2 = &mut phocode;
phocode2.name = "https://phocode.com"; // OK
println!("{}", phocode2.name); // OK: https://phocode.com
phocode.year = 2016; // lỗi: cannot assign to `phocode.year` because it is borrowed
}

Giải thích ngắn gọn là, phocode2 đang mượn tài nguyên chủ phocode, vì tài nguyên của phocode đang được cho mượn nên phocode không thể làm gì trên tài nguyên của mình được.

Biến phocode chỉ có thể truy cập lại được tài nguyên của mình khi các biến đang mượn tài nguyên của mình đã dùng xong (và tất nhiên là trả lại cho chủ), ví dụ:

struct Blog {
year: i32,
name: &’static str
}
fn main() {
let mut phocode: Blog = Blog{
year: 2017,
name: "phocode.com"
};
if phocode.year > 0 {
let phocode2 = &mut phocode;
phocode2.name = "phocode.net";
println!("{}", phocode2.name);
}
phocode.year = 2016;
println!("{}", phocode.year);
}

Trong đoạn code trên thì biến phocode2 chỉ mượn tài nguyên trong phạm vi là câu lệnh if thôi, sau khi cậu lệnh if kết thúc thì biến phocode2 được giải phóng khỏi bộ nhớ và trả lại tài nguyên cho biến phocode.

phocode.net
2016

Nhờ có cơ chế borrowingownership này mà các ứng dụng Rust ít gặp vấn đề với lỗi memory leak hơn so với trong C++.