Trong bài trước chúng ta đã biết về toán tử &
dùng để lấy địa chỉ bộ nhớ của một biến. Những biến có giá trị được lấy từ toán tử &
được gọi là con trỏ, và phép lấy địa chỉ này được gọi là tham chiếu.
Khi một biến có kiểu dữ liệu là T
thì con trỏ trỏ tới biến đó sẽ có kiểu dữ liệu là &T
. Một biến có thể được tham chiếu bởi nhiều con trỏ, ví dụ:
fn main() {
let a: u8 = 10;
let b = &a;
let c = &a;
println!("{:p}", b);
println!("{}", *c);
}
Kết quả:
0x8d0e0ffa07 10
Có thể xem trong hình minh họa dưới đây:
Nếu bạn đã từng lập trình với con trỏ và tham chiếu trong C++ thì bạn sẽ biết là chúng ta có thể thay đổi giá trị mà con trỏ đang tham chiếu tới.
Đối với Rust thì mặc định là không được phép làm điều này:
fn main() {
let a: u8 = 10;
let b =&a;
*b = 11; // lỗi: cannot assign to immutable borrowed content `*b`
}
Bởi vì làm như thế sẽ phát sinh nhiều vấn đề nghiêm trọng, tuy nhiên Rust cũng không hoàn toàn cấm chúng ta làm điều đó, chỉ cần chúng ta khai báo với Rust là biến được tham chiếu là mut và kiểu tham chiếu là &mut
như trong ví dụ sau là được:
fn main() {
let mut a: u8 = 10;
let b = &mut a;
*b =11; // OK
println!("{}", *b); // 11
println!("{}", a); // lỗi: cannot borrow `a` as immutable because it is also borrowed as mutable
a = 12; // lỗi: cannot assign to `a` because it is borrowed
}
Trong ví dụ trên, chúng ta có thể thay đổi giá trị bằng cách sử dụng *b
, nhưng khi in giá trị trong a
thì Rust vẫn báo lỗi, hay khi chúng ta cố thay đổi giá trị của a
thì cũng bị báo lỗi. Chúng ta sẽ tìm hiểu trong khái niệm borrowing ở bài sau, ở đây chúng ta chỉ cần hiểu là nếu một biến có các con trỏ tham chiếu tới nó thì biến đó sẽ bị Rust khóa lại và chúng ta không thể đọc/ghi giá trị cho biến đó được.
Ngoài ra thì Rust chỉ cho phép một con trỏ được tham chiếu theo kiểu &mut
mà thôi, ví dụ:
fn main() {
let mut a: u8 = 10;
let b = &mut a;
let c = &mut a; // lỗi: cannot borrow `a` as mutable more than once at a time
}
Rust cho phép chúng ta truyền con trỏ &mut
vào hàm, ví dụ như sau:
fn square(n: &mut i32) -> i32 {
return *n * *n;
}
fn main() {
let mut a: i32 = 10;
println!("{}", square(&mut a));
}
Trong đoạn code trên chúng ta viết hàm square()
nhận vào một con trỏ &mut
kiểu i32
, và bên trong chúng ta cho bình phương giá trị của con trỏ đó.
100
Từ khóa ref
Trong câu lệnh match
, chúng ta có thể dùng từ khóa ref
để lấy nhanh một con trỏ tới biến khóa, ví dụ:
fn main() {
let n = 42;
match n {
ref p => {
println!("p is a pointer to n, *p = {}", *p);
},
_ => println!("")
}
}
Và p
sẽ là biến con trỏ tham chiếu tới biến n
.
p is a pointer to n, *p = 42
Nếu muốn tham chiếu theo kiểu &mut
thì chúng ta khai báo ref mut
như sau:
fn main() {
let mut n = 42;
match n {
ref mut p => {
println!("p is a pointer to n, *p = {}", *p);},
_ => println!("")
}
}