Rust學習(11):所有權與函數

所有權是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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章