- vừa được xem lúc

References và Borrowing trong Rust

0 0 18

Người đăng: thiên thần gãy cánh

Theo Viblo Asia

Tiếp về vấn đề Ownership trong Rust, nhưng đã nói, sẽ rất mất công khi chúng ta muốn một hàm nhận tham số đầu vào là một biến kiểu String mà lại muốn bảo toàn Ownership cho biến đó, thật may là Rust đã giải quyết vấn đề này bằng References and Borrowing.

Immutable reference

Xét ví dụ sau đây:

fn main() { let s1 = String::from("hello"); let len = calculate_length(&s1); println!("The length of '{}' is {}.", s1, len);
} fn calculate_length(s: &String) -> usize { s.len()
}

Ở đây có ký & là biểu thị phép tham chiếu (Reference), ngoài ra trong Rust còn có * là biểu thị cho Dereference (cái từ này không biết dịch là gì, phản tham chiếu hả? Nghe ổn hông).

Minh hoạ các biến của đoạn code này trên StackHeap như sau:

Kiến thức cũ về String thì bộ 3 ptr len capacity của s1 được lưu trên Stack, giá trị "hello" được lưu trên Heap chắc vẫn nhớ ha.

Còn đối với s, hiểu nôm na là trước khi chạy logic của hàm calculate_lenght() ta có một bước s = &s1, nghĩa là s tham chiếu (reference) đến s1, mà để ghi nhớ việc tham chiếu đó thì chúng ta cần lưu lại giá trị con trỏ trỏ đến s1 cho s, mà kích thước cần dùng để lưu một ptr là cố định cho nên s cũng được lưu trên Stack.

Phép tham chiếu không chuyển ownership của giá "hello" từ s1 sang cho s, chỉ đơn giản là s đang tham chiếu đến ss1 đang sở hữu "hello" nên có thể hiểu là s đang mang giá trị "hello".

Do đó, sau khi kết thúc hàm calculate_length(), nghĩa là s đã ra khỏi scope nó, s sẽ bị drop nhưng sẽ không ảnh hưởng gì đến ownership của s1 đối với "hello" nên lệnh println!("The length of '{}' is {}.", s1, len) hoàn toàn hợp lệ.

Khi ta muốn đọc giá trị của s, Compiler sẽ xác định được nó là một tham chiếu nên sẽ truy đến tận cùng giá trị thật đang nằm trên Heap. Do đó, khi gọi pritnln!("{}", s); nó sẽ in ra "hello" chứ không phải in ra thông tin dạng {ptr: xxx, len: 5, capacity: 5}.

Đoạn này có ai thắc mắc là tại sao cùng lưu trên Stack nhưng Compiler lại biết được s là một tham chiếu, còn s1 là một biến đang sở hữu một giá trị trên Heap không? Mình đoán là do khi nhìn thấy cụm ptr len capacity đi với nhau thì biết ngay là một biến đang sở hữu một giá trị, còn khi chỉ nhìn thấy mỗi ptr thì biết nó là tham chiếu.

Mutable reference

Ở trên chỉ là Immutable reference, nghĩa là tham chiếu đến một giá trị và chỉ có quyền đọc giá trị đấy, không có quyền sửa đổi, vậy muốn sửa đổi thì làm thế nào.

Xét ví dụ sau:

fn main() { let s = String::from("hello"); change(&s);
} fn change(some_string: &String) { some_string.push_str(", world");
}

Lỗi liền nha:

$ cargo run Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference --> src/main.rs:8:5 |
7 | fn change(some_string: &String) { | ------- help: consider changing this to be a mutable reference: `&mut String`
8 | some_string.push_str(", world"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable For more information about this error, try `rustc --explain E0596`.
error: could not compile `ownership` due to previous error

Như đã nói ở bài trước, các biến trong Rust mặc định immutable (bất biến), lạ ha, biến mà lại bất biến, muốn một biến khả biến thì phải thêm mut.

Đoạn code sau hoàn hợp lệ:

fn main() { let mut s = String::from("hello"); change(&mut s); println!("{}", s) // -> hello, world
} fn change(some_string: &mut String) { some_string.push_str(", world");
}
  • Đầu tiên s được khai báo, sowner của giá trị hello
  • Gọi hàm change() với &mut s: lúc này hiểu là some_string = &mut s, giá trị mà some_string mang là hello
  • some_string.push_str(", world"): push thêm ", world" và giá trị mà some_string đang mang, lúc này thành "hello, world"
  • Tuy nhiên giá trị vừa được cập nhật đó vốn luôn thuộc về s, cho nên khi kết thúc hàm thì some_string bị drop nhưng "hello, world" vẫn là giá trị của s

Nguyên tắc khi dùng reference

Có một nguyên tắc duy nhất là: khi đang có một tham chiếu &mut đang tồn tại thì không một tham chiếu nào khác kể cả &mut hay & được phép tồn tại.

fn main() { let mut s = String::from("hello"); let r1 = &mut s; let r2 = &mut s; println!("{}, {}", r1, r2);
} Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0499]: cannot borrow `s` as mutable more than once at a time --> src/main.rs:5:14 |
4 | let r1 = &mut s; | ------ first mutable borrow occurs here
5 | let r2 = &mut s; | ^^^^^^ second mutable borrow occurs here
6 | 7 | println!("{}, {}", r1, r2); | -- first borrow later used here For more information about this error, try `rustc --explain E0499`.
error: could not compile `ownership` due to previous error

Lỗi: Khi cả r1r2 đều muốn thay đổi giá trị của s thì s biết nghe đứa nào.

fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem let r3 = &mut s; // BIG PROBLEM println!("{}, {}, and {}", r1, r2, r3);
} Compiling ownership v0.1.0 (file:///projects/ownership)
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable --> src/main.rs:6:14 |
4 | let r1 = &s; // no problem | -- immutable borrow occurs here
5 | let r2 = &s; // no problem
6 | let r3 = &mut s; // BIG PROBLEM | ^^^^^^ mutable borrow occurs here
7 | 8 | println!("{}, {}, and {}", r1, r2, r3); | -- immutable borrow later used here For more information about this error, try `rustc --explain E0502`.
error: could not compile `ownership` due to previous error

Lỗi: r1r2 đang tham chiếu đến sr3 lại thay đổi giá trị của s thì r1, r2 biết đường nào mà lần

Nhưng nếu sửa thành:

fn main() { let mut s = String::from("hello"); let r1 = &s; let r2 = &s; let r3 = &mut s; println!("{}", r3);
}

thì chương trình hoàn toàn chạy được, do Compiler xác định được r1r2 đã không còn được dùng ở bất cứ đâu kể từ dòng khai báo của r3.

Cũng tương tự, đoạn code sau hoàn toàn hợp lệ:

fn main() { let mut s = String::from("hello"); let r1 = &s; // no problem let r2 = &s; // no problem println!("{} and {}", r1, r2); // variables r1 and r2 will not be used after this point let r3 = &mut s; // no problem println!("{}", r3);
}

Dangling References

Buồn ngủ quá mấy bồ ơi, mai viết tiếp, G9!

Bình luận

Bài viết tương tự

- vừa được xem lúc

Chuyện cái comment

Chuyện cái comment. Chuyện rằng, có một ông bạn nọ có cái blog ở trên mạng, cũng có dăm.

0 0 31

- vừa được xem lúc

Đừng đánh nhau với borrow checker

Đừng đánh nhau với borrow checker. TL;DR: Đừng bao giờ đánh nhau với borrow checker, nó được sinh ra để bạn phải phục tùng nó .

0 0 28

- vừa được xem lúc

Chuyện biểu diễn ma trận trên máy tính

Chuyện biểu diễn ma trận trên máy tính. Cách đây mấy hôm mình có share cái screenshot trên Facebook, khoe linh tinh vụ mình đang viết lại cái CHIP-8 emulator bằng Rust.

0 0 43

- vừa được xem lúc

Rust và Lập trình Web

Rust và Lập trình Web. Bài viết được dịch lại từ bản gốc "Rust for the Web" đăng tại phiên bản blog tiếng Anh của mình.

0 0 36

- vừa được xem lúc

Viết ứng dụng đọc tin HackerNews bằng Rust

Viết ứng dụng đọc tin HackerNews bằng Rust. Dạo này mình toàn viết bài linh tinh, lâu rồi chưa thấy viết bài kĩ thuật nào mới nên hôm nay mình viết trở lại, mất công các bạn lại bảo mình không biết co

0 0 24

- vừa được xem lúc

Cài đặt Rust trên Arch Linux

Cài đặt Rust trên Arch Linux. Việc cài đặt Rust trên môi trường Arch Linux khá là đơn giản.

0 0 39