大家好今天要介紹的是 Reference and Borrowing(參照與借用),上一篇在最後的時候有提到如果所有權已經被轉移給了函式但是原本的 scope 還是需要使用時就必須每次在函式最後把值再回傳回來,而這對於一般的使用情況不是很友善會很頻繁的需要回傳,所以本章要介紹的就是 references 並且用他更好的處理這件事情。
首先我們來稍微改寫一下上一篇的例子,
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 傳遞 reference 但是沒有 ownership
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
} // 因為沒有任何 ownership 所以 drop 也不會怎樣
各位注意到有什麼變化了嗎?
首先
我們借官網的圖來看比較好懂,
因此 references(參照) 字面上來說就跟實際行為蠻像的,感覺就是請你參照他就好但我不必擁有他。
那麼如果我們想要修改 borrowing(借)來的 references 的呢?舉個例子,
fn main() {
let s = String::from("hello");
unchangeable(&s);
}
fn unchangeable(some_string: &String) {
some_string.push_str(", world");
^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}
在編譯時就會報錯,因為這個 reference 指向的是 immutable 的變數所以我們並不能修改他。
前面的例子除非改成 mutable 才可以修改,例如,
fn main() {
let mut s = String::from("hello");
changeable(&s);
println!("s = {}", s); // s = hello, world
}
fn changeable(some_string: &mut String) {
some_string.push_str(", world");
}
這樣就可以編輯,但是 Rust 對這樣的寫法有很大的限制,在同一個 scope 不能同時宣告 2 個以上的 mutable reference 因此像是這個例子就會報錯,
fn cause_error() {
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
^^^^^^ second mutable borrow occurs here
println!("{}, {}", r1, r2);
}
而主要的原因是 Rust 要避免在編譯期間造成 data race, data race 類似於 race condition 他通常發生於下列三種情況,
這裡有 wiki 的補充資料可以更了解 race condition,簡單來說就是處理資料時的先後順序沒處理好而導致資料錯誤的情況。
基本上 data race 會造成 undefined 而且不容易找出 bug,而這也是 Rust 希望可以幫助工程師避免的情況,所以才會有這樣的限制,基本上就算沒有限制筆者也會避免這樣的設計方式,除非有非常不得已的理由。
同樣的錯誤也會出現在同時擁有 immutable 跟 mutable reference 的 scope 裡面例如,
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
let r3 = &mut s; // BIG PROBLEM
^^^^^^ mutable borrow occurs here
println!("{}, {}, and {}", r1, r2, r3);
這個錯的情況很容易理解,我們不能給了 immutable 的 reference 之後又他給 mutable 的 reference 你到底是要我變還是不變呢?
另外是 reference 的 scope 從被宣告開始到最後一次被使用之後就結束了所以像是這個例子是可以的,
fn this_work() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// 在這之後不再使用 r1 和 r2
let r3 = &mut s; // no problem
println!("{}", r3);
}
同要的道理我先 mutable 再 immutable 也是可以的例如,
fn this_still_work() {
let mut s = String::from("hello");
let r1 = &mut s;
println!("{}", r1);
// 在這之後不再使用 r1
let r2 = &s;
let r3 = &s;
println!("{} {}", r2, r3);
}
還有如果在不同 scope 例如這樣也是可以的,
fn this_work_too() {
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
}
好在 Rust 限制得很嚴格不然我可能就會寫出很多有問題的程式碼,這點也是我喜歡 Rust 的原因就算編譯慢一點難學一點也沒關係啊。
迷途指標,筆者我比較喜歡這個翻法比較直覺,簡單來說就是值已經被釋放了但是 reference 沒有收回來。
不過完全不用擔心因為 Rust 說保證不會發生這個情況他會在編譯時幫我們做檢查,來看這個例子,
fn main() {
let reference_to_nothing = dangling_references();
}
// help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
fn dangling_references() -> &String {
^ help: consider giving it a 'static lifetime: `&'static`
let s = String::from("hello");
&s
} // s 在這邊已經離開 scope 了但是卻回傳了他的 reference,因此就會造成迷途指標。
這邊要這樣改才會正常
fn no_dangling_references() -> String {
let s = String::from("hello");
s
}
今天一口氣介紹完了 reference 的概念,包含 Rust 的安全機制還有底層記憶體的指向方式以及怎麼避免錯誤的寫法。下一篇會介紹另一個 ownership 的概念 slice type。
那麼今天也告一段落拉,我們明天見。