rust所有权转移-引用和借用

发表于 · 归类于 代码 · 阅读完需 18 分钟 · 报告错误 · 阅读:

引用和借用

引用(reference)不获取所有权,坚持单一所有者和单一职责,解决了共享访问障碍。按引用传递对象的方式称作借用(borrow),这比转移所有权更有效。

fn main() {
    let a = 100;
    let r: &i32 = &a;          // &T: 不可变引用。

    assert_eq!(*r, a);
}
fn main() {
    let mut a = 100;
    let r: &mut i32 = &mut a;  // &mut T: 可变引用。
    *r += 1;

    assert_eq!(*r, 101);
}

 

实现

引用的底层实现和指针基本一致,也就是说附加于引用身上的规则是编译器需要处理的。

两者区别在于:

  1. 引用必须有效。引用失效前,目标必须存活且不可改变。
  2. 而指针未必,可能是空指针、野指针或悬垂指针。
fn main() {
    let mut a = 0;
    let _r = &mut a;
}
// /root/rs/closure/src/main.rs:
fn main() {
    0x0000555555559200 <+0>:    sub    rsp,0x10

    // let mut a = 0;
    0x0000555555559204 <+4>:    mov    DWORD PTR [rsp+0x4],0x0

    // let _r = &mut a;
    0x000055555555920c <+12>:   lea    rax,[rsp+0x4]                // 引用就是一个指针传递
    0x0000555555559211 <+17>:   mov    QWORD PTR [rsp+0x8],rax

    // }
=> 0x0000555555559216 <+22>:    add    rsp,0x10
   0x000055555555921a <+26>:    ret
End of assembler dump.

 

引用不获取所有权,也不关心释放。

fn test(s: &mut String) {
    s.push_str("!!!") ;
}

fn main() {
    let mut s = String::from("hello");
    test(&mut s);
}
; /root/rs/closure/src/main.rs:
    // fn main() {
    0x000055555555ada0 <+0>:    sub    rsp,0x28
    0x000055555555ada4 <+4>:    lea    rax,[rip+0x332c1]        # 0x55555558e06c

    // let mut s = String::from("hello");
    0x000055555555adab <+11>:   mov    rdi,rsp
    0x000055555555adae <+14>:   mov    rsi,rax
    0x000055555555adb1 <+17>:   mov    edx,0x5
    0x000055555555adb6 <+22>:   call   0x55555555c8c0 <<alloc::string::String as core::convert::From<&str>>::from>
    0x000055555555adbb <+27>:   mov    rdi,rsp

    // test(&mut s);
 => 0x000055555555adbe <+30>:   call   0x55555555ad80 <closure::test>
    0x000055555555adc3 <+35>:   jmp    0x55555555adc5 <closure::main+37>

    // }
    0x000055555555adc5 <+37>:   mov    rdi,rsp
    0x000055555555adc8 <+40>:   call   0x55555555bc80 <core::ptr::drop_in_place>                ; 引用不获取所有权
    0x000055555555adcd <+45>:   add    rsp,0x28
    0x000055555555add1 <+49>:   ret
    0x000055555555add2 <+50>:   mov    rdi,rsp
    0x000055555555add5 <+53>:   call   0x55555555bc80 <core::ptr::drop_in_place>

    // fn main() {
    0x000055555555adda <+58>:   mov    rdi,QWORD PTR [rsp+0x18]
    0x000055555555addf <+63>:   call   0x555555559050 <_Unwind_Resume@plt>
    0x000055555555ade4 <+68>:   ud2
    0x000055555555ade6 <+70>:   mov    QWORD PTR [rsp+0x18],rax
    0x000055555555adeb <+75>:   mov    DWORD PTR [rsp+0x20],edx
    0x000055555555adef <+79>:   jmp    0x55555555add2 <closure::main+50>


// 作为s参数传入,并不会发生所有权转移

// Dump of assembler code for function closure::test:
// /root/rs/closure/src/main.rs:

//   fn test(s: &mut String) {
 => 0x000055555555ad80 <+0>:    push   rax
    0x000055555555ad81 <+1>:    lea    rax,[rip+0x332e1]        # 0x55555558e069
    0x000055555555ad88 <+8>:    mov    QWORD PTR [rsp],rdi

    // s.push_str("!!!");
    0x000055555555ad8c <+12>:   mov    rsi,rax
    0x000055555555ad8f <+15>:   mov    edx,0x3
    0x000055555555ad94 <+20>:   call   0x55555555c860 <alloc::string::String::push_str>

    // }
    0x000055555555ad99 <+25>:   pop    rax
    0x000055555555ad9a <+26>:   ret

 

引用和指针区别

区分引用和原始指针(raw pointer)的区别。

引用声明也用 &T,指针 *T

fn main() {
    // reference
    let mut x = 100;
    let r : &mut i32 = &mut x;

    *r += 1;
    println!("{:?}", *r);

    // raw-pointer
    let mut y = 200;
    let p: *mut i32 = &mut y;

    unsafe {                        // 指针需要使用unsafe进行操作
        *p += 1;
        println!("{:?}", *p);
    }
}

 

规则

引用与指针最大的区别在于附加以下的规则,这些需要编译器作出检查。

 

可以按下面的逻辑理解:

值有效范围。

fn main() {
    let y: &i32;

    {
        let x = 5;
        y = &x;
            // ^^ borrowed value does not live long enough
    }
    // - `x` dropped here while still borrowed

    println!("{}", y);
}

 

变量冻结

在借用未失效的情况下,改变原绑定,会导致不一致行为。

这与其他语言有很大的区别。

fn main() {
    let mut s = String::from("abc");
    let r = &s;
            // -- immutable borrow occurs here

     // 引用未失效,不能修改
    s.push_str("def");             
            // ^^^^^^^^^^^^^^^^^ mutable borrow occurs here

    println!("{:?}", r);
            // - immutable borrow later used here
}
fn main() {
    let mut a = 1;

    // 或 &mut a
    let r = &a;            
            // -- borrow of `a` occurs here

    // 引用未失效,不能重新赋值
    a = 50;                 
            // ^^^^^^ assignment to borrowed `a` occurs here

    println!("{}", r);
                   // - borrow later used here
}
fn main() {
    let mut a = 1;
    let r = &a;             // 或&mut a

    println!("{}", r);      // 后续不再使用

    a = 50;                 // OK!
}

 

同一时刻,不能同时有可变和不可变借用。

fn main() {
    let mut x = 1;
    let r = &mut x;

    println!("{}{}", x, r);
                     // ^  - mutable borrow later used here
                     // |
                     // immutable borrow occurs here
}
fn main() {
    let mut x = 5;
    let r = &mut x;

    *r += 1;

    println!("{}", x);
                   // ^ immutable borrow occurs here 
    println!("{}", *r);
                   // -- mutable borrow later used here
}
fn main() {
    let mut x = 5;
    
    {
        let r = &mut x;
        *r += 1;
        println!("{}", *r);
    }                           // 可变借用结束

    println!("{}", x);
}

 

同一时刻,只能有一个可写。

fn main() {
    let mut x = 1;
    let r1 = &mut x;
             // ------ first mutable borrow occurs here
    let r2 = &mut x;
             // ^^^^^^ second mutable borrow occurs here

    println!("{}{}", r1, r2);
            // -- first borrow later used here
}

值可变和引用自身可变的区别。

和指针一样,引用自身也是一个对象,有存储空间。

调试器或反汇编,看到引用与指针一样.

 

fn main() {
    let mut x = 100;
    let r = &mut x;             // 值可变,let r: &mut i32 = &mut x;
    *r += 1;
    
    assert_eq!(*r, 101);
}
fn main() {
    let x = 100;
    let y = 200;

    let mut r = &x;             // 引用自身可变。 let mut r: &i32 = &x;
    assert_eq!(*r, x);

    r = &y;                     // 引用绑定新对象
    assert_eq!(*r, y);
}