引用和所有权借用

所有权不仅可以转移(原变量会丢失数据的所有权),还可以通过引用的方式来借用数据的所有权(borrow ownership)。

使用引用借用变量所有权时,【借完】之后会自动交还所有权,从而使得原变量不丢失所有权。至于什么时候【借完】,尚无法在此深究。

例如:

fn main(){
  {
    let s = String::from("hello");
    let sf1 = &s; // 借用
    let sf2 = &s; // 再次借用
    println!("{}, {}, {}",s, sf1, sf2);
  }  // sf2离开,sf1离开,s离开
}

注意,&s表示创建变量s所指向数据的引用,为某个数据创建引用的过程不会转移该数据的所有权。

引用保存在栈中,其实现了Copy Trait,因此下面的代码是等价的:


#![allow(unused)]
fn main() {
// 多次创建s所指向数据的引用,
// 并将它们赋值给不同变量
let sf1 = &s;
let sf2 = &s;

// 拷贝sf1,使得sf2也引用s,
// 但sf1是引用,是可Copy的,因此sf1仍然有效,即仍然指向数据
let sf1 = &s;
let sf2 = sf1;
}

还可以将变量的引用传递给函数的参数,从而保证在调用函数时变量不会丢失所有权。

fn main(){
  let s = String::from("hello");
  let s1 = s.clone();

  // s1丢失所有权,s1将回到未初始化状态
  f1(s1); 
  // println!("{}", s1);

  // 传递s的引用,借用s所有权 
  let l = f2(&s);
              // 交还所有权
  // s仍然可用
  println!("{} size: {}", s, l);
}

fn f1(s: String){
  println!("{}", s);
}

fn f2(s: &String)->usize{
  s.len()   // len()返回值类型是usize
}

可变引用和不可变引用的所有权规则

变量的引用分为可变引用&mut var和不可变引用&var,站在所有权借用的角度来看,可变引用表示的是可变借用,不可变引用表示的是不可变借用。

  • 不可变借用:借用只读权,不允许修改其引用的数据
  • 可变引用:借用可写权(包括可读权),允许修改其引用的数据
  • 多个不可变引用可共存(可同时读)
  • 可变引用具有排他性,在有可变引用时,不允许存在该数据的其他可变和不可变引用
    • 这样的说法不准确,短短几句话也无法描述清楚,因此留在后面再详细解释

前面示例中f2(&s)传递的是变量s的不可变引用&s,即借用了数据的只读权,因此无法在函数内部修改其引用的数据值。

如要使用可变引用去修改数据值,要求:

  • var的变量可变,即let mut var = xxx
  • var的引用可变,即let varf = &mut var

例如:

fn main(){
  let mut x = String::from("junmajinlong");
  let x_ref = &mut x;  // 借用s的可写权
  x_ref.push_str(".com");
  println!("{}", x);

  let mut s = String::from("hello");
  f1(&mut s);   // 借用s的可写权
  println!("{}", s);
}

fn f1(s: &mut String){
  s.push_str("world");
}

容器集合类型的所有权规则

前面所介绍的都是标量类型的所有权规则,此处再简单解释一下容器类型(比如tuple/array/vec/struct/enum等)的所有权。

容器类型中可能包含栈中数据值(特指实现了Copy的类型),也可能包含堆中数据值(特指未实现Copy的类型)。例如:


#![allow(unused)]
fn main() {
let tup = (5, String::from("hello"));
}

容器变量拥有容器中所有元素值的所有权

因此,当上面tup的第二个元素的所有权转移之后,tup将不再拥有它的所有权,这个元素将不可使用,tup自身也不可使用,但仍然可以使用tup的第一个元素。


#![allow(unused)]
fn main() {
let tup = (5, String::from("hello"));

// 5拷贝后赋值给x,tup仍有该元素的所有权
// 字符串所有权转移给y,tup丢失该元素所有权
let (x, y) = tup;    
println!("{},{}", x, y);   // 正确
println!("{}", tup.0);     // 正确
println!("{}", tup.1);  // 错误
println!("{:?}", tup);  // 错误
}

如果想要让原始容器变量继续可用,要么忽略那些没有实现Copy的堆中数据,要么clone()拷贝堆中数据后再borrow,又或者可以引用该元素。


#![allow(unused)]
fn main() {
// 方式一:忽略
let (x, _) = tup;
println!("{}", tup.1);  //  正确

// 方式二:clone
let (x, y) = tup.clone();
println!("{}", tup.1);  //  正确

// 方式三:引用
let (x, ref y) = tup;
println!("{}", tup.1);  //  正确
}