1. 程式人生 > 其它 >rust中&與ref

rust中&與ref

&操作符

&操作符用於變數名前,表示建立此變數的引用,用於型別名前,表示此型別的借用型別。

let x = vec![1, 2, 3]; // vec不具備複製特性。賦值時不會自動複製。
let y = &x; // &x建立對x的引用,再繫結到y,y的型別是&Vec<i32>。
let z: &Vec<i32> = &x; //等同上一行

fn some_fun(arg: &T) {} // &T是一個型別,表示arg是對T型別的引用。

當&出現在=左側時,可以用於模式匹配,但通常這種寫法都是錯的,僅在具備複製特性的型別時才能通過編譯。

具備複製特性的型別

// 具備複製特性的型別,如i32
let x = 100;
let &y = &x; // &x建立對x的引用(&i32型別),再通過模式匹配,與y進行繫結。
// 用&y匹配&i32型別,同時將=右側解構,得到&i32型別,相當於將右側的i32複製到左側的i32。
// 因為i32可以複製,所以可以成功繫結。

不具備複製特性的型別

// 不具備複製特性的型別,如Vec<i32>
let x = vec![1, 2, 3];
let &y = &x; // 出錯,&x建立對x的引用(&Vec<i32>型別),再通過模式匹配,與y進行繫結。
// 繫結時&y匹配&Vec<i32>型別,所以y就是Vec<i32>型別,=右側也對&x的型別進行解構,也得到&Vec<i32>,
// 於是這上繫結就相當於將右側的Vec<i32>賦給左側的Vec<i32>,
// 因為Vec<i32>不能複製,所以這裡是移動語義,相當於把x移動到y。
// 但因為&x本身建立了對x的引用(臨時的引用),不能移動x。出錯資訊:
// error[E0507]: cannot move out of a shared reference
//   --> src/bin/test1.rs:11:14
//    |
// 11 |     let &u = &x;
//    |         --   ^^
//    |         ||
//    |         |data moved here
//    |         |move occurs because `u` has type `Vec<i32>`, which does not implement the `Copy` trait
//    |         help: consider removing the `&`: `u`

上述出錯的程式碼相當於:

let x = vec![1, 2, 3];
let y = &x;
let &z = y; // 對y進行解構,得到&Vec<i32>,於是嘗試將y所指向的x移動到z,但因為y的存在不能成功。

ref關鍵字

某些情況下,使用ref與&是等效的,但ref關鍵字一般用於要繫結的識別符號前,通常這些地方都無法使用&。

let x = vec![1, 2, 3];
let ref y = x; // ref表示以引用的方式繫結y和x,所以y的型別是&Vec<i32>。
let y = &x; // 與上一句等價。

ref用於match的分支匹配中

match在匹配時,會拿走被匹配變數,所以match之後,原變數無法再使用。

let a = Some(vec![1, 2, 3]);
match a {
        None => (),
        Some(value) => println!("{:?}", value), // value繫結是移動語義,所以a被match佔據。
}
println!("{:?}", a); // 不能再使用a

要避免這種情況,可以使用ref,


let a = Some(vec![1, 2, 3]);
match a {
        None => (),
        Some(ref value) => println!("{:?}", value), // value以引用方式繫結。
}
println!("{:?}", a); // a仍然在

// 也可以寫成

let a = Some(vec![1, 2, 3]);
match &a { // 建立a的引用,match只使用了a引用,且match結束時,引用離開作用域也會銷燬
        None => (),
        Some(value) => println!("{:?}", value), // 編譯器會識別出,value也是引用。
}
println!("{:?}", a); // 可以繼續使用a,如果a可變,也可以修改a

ref用於解構時引用結構體某些欄位

不考慮複製特性,結構體進行匹配時,預設將使用移動語義,結構體欄位所有權從結構體移動到被繫結的變數上,原變數失去所有權。例如

#[derive(Debug)]
struct Person {
    name: String,
}

fn main() {
    let mut p = Person {
        name: "薛海舟".to_string(),
    };

    let Person { name: a } = p; // a是被繫結的識別符號,這裡將p中name的所有權轉移給a變數。
    p.name.push_str("舒珊靖"); // 失敗,因為p已經被移動。
    println!("{:?}", a); // a中內容是"薛海舟"
}

使用ref就表示,要以引用的形式來繫結。例如

#[derive(Debug)]
struct Person {
    name: String,
}

// ref只能用於要被繫結的識別符號前
fn main() {
    let mut p = Person {
        name: "薛海舟".to_string(),
    };

    let Person { name: ref mut a } = p; // a是是對p中name欄位的可變借用
    *a = "this one".to_string(); // 對a賦值,就相當於對p中name操作
    println!("舒珊靖", p); // p中name變為舒珊靖
}