所有權是Rust最獨特的功能,其令Rust無需垃圾回收即可保障內存安全
什麼是所有權
所有運行的程序都必須管理其使用計算機內存的方式。一些語言中具有垃圾回收機制,在程序運行時不斷地尋找不再使用地內存;在另一些語言中,程序員必須親自分配和釋放內存。Rust則選擇了第3種方式:通過所有權系統管理內存,編譯器在編譯時會根據一系列地規則進行檢查。在運行時,所有權系統地任何功能都不會減慢程序
所有權系統要處理的問題:
- 跟蹤哪部分代碼正在使用堆上的哪些數據
- 最大限度的減少堆上重複數據的數量
- 清理堆上不再使用的數據確保不會耗盡空間。
所有權的存在就是爲了管理堆數據。一旦理解了所有權,就不需要經常考慮棧和堆了
所有權規則:
- Rust中的每一個值都有一個被稱爲所有者的變量
- 值有且只有一個所有者
- 當所有者(變量)離開作用域,這個值將被丟棄
所有權與函數傳參
fn main() {
let s = String::from("Hello world"); // s 進入作用域
takes_ownership(s); // 所有權系統將s的值交給s_str,然後s就失效了,此後不能在訪問s
// println!("{}", s); //error[E0382]: borrow of moved value: `s`
//String是堆上的數據,因此有所有權系統管理。
let x = 5; // x 進入作用域
makes_copy(x); // i32 是 Copy 的,所以在後面可繼續使用 x:xx將x綁定的值複製一份,並且綁定到s_int變量中
println!("{}", x);
} //這裏, x 先移出了作用域,然後是 s。但因爲 s 的值已被移走,所以不會有特殊操作
fn takes_ownership(s_str:String){// some_string 進入作用域
println!("{}", s_str);
} // 這裏,some_string 移出作用域並調用 `drop` 方法。佔用的內存被釋放
fn makes_copy(s_int:i32){ // some_integer 進入作用域
println!("{}", s_int);
} // 這裏,some_integer 移出作用域。不會有特殊操作
總結:在堆上的數據由所有權系統管理,函數傳參時所有權系統會將值的所有權交給被調用函數的形參,此時,原先綁定值變量失去了自己的值,就失效了
在棧上的數據傳值時是傳副本,原先綁定值變量並沒有失去自己的值,依然可以被訪問
不可變變量和不可變引用
1、不可變變量不可以修改所綁定的值,不可變引用只能向第一個人借值,如果借到了就不能再向其他人借值了。
fn main() {
let s1 = String::from("Hello"); //s1進入作用域
let s = &s1; //引用s1:創建一個棧空間s,指向s1,也就是s裏面存儲s1的地址
let len = calculate_length(s); //s是copy的。將s的值也就是s1的地址拷貝一份,並且綁定到 calculate_length的形參s上
println!("the length of '{}' is {}", s1, len); //the length of 'Hello' is 5
println!("{}", s) //Hello
}
fn calculate_length(s:&String)->usize{ //s是對String的引用
// s.push_str(", world"); error //因爲s實質是對不可變變量的引用,因此不允許改變其值
s.len()
} //當形參s離開當前作用域時,它綁定的值也就是String的地址被丟棄,但是不影響實際的String,其有有權沒有被變更過
我們將獲取引用作爲參數參數稱爲借用。 正如現實生活中,如果一個人擁有某樣東西,你可以從他那裏借來。當你使用完畢,必須還回去。
默認不允許修改借來的值
規則:默認不允許修改引用的值
fn main() {
let s1 = String::from("Hello");
change(&s1);
println!("s1 = {} ", s1);
}
fn change(s:& String){
// s.push_str(" world") error //因爲s實質是對不可變變量的引用,因此不允許改變其值
println!("s = {} ", s);
}
fn main() {
let s1 = String::from("Hello"); //s1不可變
let r = & s1;
let s2 = String::from("Hello");
// r = & s2; error:r是不可變的
}
不可變變量和不可變引用皆不可改變
2、不可變變量允許多個不可變引用同時借值,反正大家都不能改變這個值–>全部幹看着吧
fn main() {
let s1 = String::from("Hello"); //s1不可變
let r1 = &s1;
let r2 = &s1;
println!("{}, {} ", r1, r2); //Hello
}
3、不可變變量可以借值給可變引用和不可變引用,但是不可變變量只有一種借值方法:就是不允許可變引用和不可變引用修改這個值。
可變變量和不可變引用
可變變量有兩種借值方法:允許改變其值或者不允許改變其值.
不允許借值者修改值
fn main() {
let mut s1 = String::from("Hello"); //s1可變
s1.push_str(" rust");
let r1 = &s1;
nochange(r1);
println!("{}", r1); //Hello rust
}
fn nochange(r:&String){
// r.push_str("world"); //error[E0596]: cannot borrow `*r` as mutable, as it is behind a `&` reference
println!("{}", r) //Hello rust
}
給借值者修改值的權限
fn main() {
let mut s1 = String::from("Hello"); //s1可變
s1.push_str(" rust");
let r1 = &mut s1;
nochange(r1);
println!("{}", r1); //Hello rustworld
}
fn nochange(r:&mut String){
r.push_str("world");
println!("{}", r) //Hello rustworld
}
不可變變量與可變引用
可變引用可以多個人借錢,包括可變變量和不可變變量[任意順序]
fn main() {
let s1 = String::from("Hello"); //s1不可變
let mut r = & s1; //r是可變的
println!("{} ", r); //Hello
let s2 = String::from("world");
r = & s2;
println!("{}", r); //"world"
}
一個可變引用向不可變變量借值,可變引用沒有權利對這個值做出改變
fn main() {
let s1 = String::from("Hello"); //s1不可變
let mut r = & s1; //r是可變的: warning: variable does not need to be mutable。help: remove this `mut`
try_change(r);
println!("{} ", r); //Hello
}
fn try_change(r:&String){
println!("{}",r);
}
可變變量和可變引用
fn main() {
let mut s1 = String::from("Hello");
change(&mut s1);
println!("s1 = {} ", s1);
}
fn change(s:&mut String){
s.push_str(" world") ; //因爲s實質是針對一塊可變變量的引用
}
總結:mut給予了權限:允許你改變借來的東西
總結:
1、可變引用可以向多個人[變量]借錢[值],不可變引用只能向一個人[變量]借錢[值],當不可變引用第一次借到錢[值]之後,就不能在像其它[變量]人借錢[值]
可變引用可以向不可變變量和可變變量借值:
- 不可變變量–>可變引用:可變引用無權改變值,不可變變量不可以改變值。此時可變引用實質應該寫成不可變引用,也就是應該爲不可變變量–>不可變引用
- 可變變量->可變引用:可變引用有權改變其值,可變變量可以改變值
- 不可變變量–>不可變引用&可變變量->不可變引用:不可變引用可以向可變變量或者不可變變量借錢,反正都不能修改,但是不可變引用只允許向一個變量借錢,如果已經借到了,就不能像其他人借錢了。區別在於可變變量可以改變值,不可變變量不可以改變值
其他:可變變量的原則(。・∀・)ノ
編寫一個函數,該函數接收一個字符串,並返回在該字符串中找到的第一個單詞。如果沒有找,返回空
fn main() {
let s = "string is composed of char".to_string();
//s.clear(); //cannot borrow `s` as mutable, as it is not declared as mutable:s是不可變的,因此不能調用這個方法,如果想要調用這個方法,必須let mut s
let res = first_word(&s); //獲取 &String 而不是 String,沒有得到值得所有權
println!("{}", res);
}
fn first_word(s:&String)->&str{
for (i, &item) in s.as_bytes().into_iter().enumerate(){
if item == b' '{ //空格是一個char,因此需要' '
return &s[0..=i]; //返回一個引用,這個引用指向傳入得那一片內存得一小塊[ptr相同但是len和cap不相同]
}
}
return ""
}
可變變量的值如果曾經以不可變權限借值出去的話,就不能再以可變權限借值出去
【第一次借值出去是不允許別人修改,那麼這個值就是不能再讓別人修改了】—>真有原則
當然,如果先前給予別人修改值得權限,再次借給別人得時候可以收回值得權限,但是已經收回值得權限了,就不能再給人修改值得權限了
fn main() {
let mut s = "string is composed of char".to_string();
s = "123 456".to_string(); //可以修改
let res = first_word(&s); //獲取 &String 而不是 String,沒有得到值得所有權
s = "123".to_string();//error[E0506]: cannot assign to `s` because it is borrowed:值被借出去了,還沒有還回來,因此不允許修改
s.clear(); //cannot borrow `s` as mutable because it is also borrowed as immutable:說了不許修改了,以後
println!("{}", res);
}
fn first_word(s:&String)->&str{
for (i, &item) in s.as_bytes().into_iter().enumerate(){
if item == b' '{ //空格是一個char,因此需要' '
return &s[0..=i]; //返回一個引用,這個引用指向傳入得那一片內存得一小塊[ptr相同但是len和cap不相同]
}
}
return ""
}
&str是一個指向二進制程序特定位置的slice。如果有一個String,可以傳遞整個String的slice。定義一個獲取字符串slice而不是String引用的函數可以使API更加通用。
enumerate包裝iter的結果並且返回一個元組,其中每一個元素是元組的一部分。enumerate返回元組的第一個元素是索引,第二個元素是集合中元素的引用。
fn main() {
let s = "string is composed of char".to_string();
let res = first_word(&s[..]); //獲取 &String 而不是 String,沒有得到值得所有權
println!("{}", res);
}
fn first_word(s:&str)->&str{
for (i, &item) in s.as_bytes().into_iter().enumerate(){
if item == b' '{ //空格是一個char,因此需要' '
return &s[0..=i]; //返回一個引用,這個引用指向傳入得那一片內存得一小塊[ptr相同但是len和cap不相同]
}
}
return ""
}
所有權與返回值
fn main() {
let s1 = gives_ownership(); //gives_ownership()返回一個值,這個值的所有權將轉交給s1
let s2 = String::from("Hello");
let s3 = takes_and_gives_back(s2); //s2所綁定的值的的所有權轉交給了形參a_string,此後s2失效,不可以再訪問。s3獲取到值得所有權
//println!("{}", s2); //error[E0382]: borrow of moved value: `s2`
println!("{}, {}", s1, s3);
} //s3移除作用域其值將通過drop被清理掉,s2已經被移出了因此什麼操作也不會發生。s1移出作用域,其值將通過drop被清理掉
fn gives_ownership()->String{
let s_str = String::from("Hello"); //s_str進入作用域
s_str
}//將s_str的值轉給調用的函數,s_str沒有了綁定的值,s_str失效。
fn takes_and_gives_back(a_string:String)->String{ //a_string進入作用域
a_string //將a_string綁定的值轉交給調用的函數
}
一塊內存的所有者只能有一個
變量的所有權:將值賦給另一個變量時移動它。當持有堆中數據值得變量離開作用域時,其值將通過drop被清理掉,除非數據被移動爲另一個變量所有。
參考:https://kaisery.github.io/trpl-zh-cn/ch04-01-what-is-ownership.html