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ế borrowing và ownership 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++.