天天看點

Rc與Box差別

咋一看覺得這兩個概念差的很遠,其實他們之間還是有聯系的:

  • 相同點:

    他們都是在堆上配置設定資源,都隻是儲存了一個指向堆上的指針

  • 差別:
    • Box就是一個簡單的指向堆的指針,并且指向堆的這個區域的指針隻能有一個,owner lifetime結束就會釋放堆上的資源
    • Rc增加了一個計數(準确來說是兩個指針),可以多個指針指向堆的這個區域,等所有的引用者lifetime結束了才釋放堆上的資源

下面的代碼為了看起來清爽點删除了注釋

1 這個是Rc的定義

struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    weak: Cell<usize>,
    value: T,
}

pub struct Rc<T: ?Sized> {
    _ptr: NonZero<*mut RcBox<T>>,
}
           

其中NonZero很簡單,就是簡單的包裝了下T

pub struct NonZero<T: Zeroable>(T);
           

具體可以看:

http://doc.rust-lang.org/stable/src/alloc/rc.rs.html#172

2 Box定義如下:

pub struct Box<T>(Unique<T>);
           

http://doc.rust-lang.org/stable/src/alloc/boxed.rs.html#94

其中Unique定義如下:

pub struct Unique<T: ?Sized> {
    pointer: NonZero<*const T>,
    marker: PhantomData<T>,
}
           

http://doc.rust-lang.org/stable/src/core/ptr.rs.html#513-521

Unique裡的marker是不占用存儲空間的,可以暫時忽略掉

3 具體的例子:

use std::rc::Rc;

#[derive(Debug)]
struct Foo(i32);


fn main(){
    let r:Rc<Foo>;
    let b:Box<Foo>;
    {
        let r1 = Rc::new(Foo());
        println!("r1:{:p}",&*r1);
        //clone計數
        r = r1.clone();
        //多處引用,都可以分别通路
        println!("r1:{:?}",r1);

        let b1 = Box::new(Foo());
        println!("b1:{:p},data:{}",&*b1,(*b1).);
        //owner ship 轉移了
        b = b1;
        //b1不能再通路
        //println!("{:?}",b1);
    }
    println!("{:?}",r);
    println!("r:{:p}",&*r);

    println!("{:?}",b);
    println!("b:{:p}",&*b);
}
           

Play位址:http://is.gd/rGBLaW

輸出結果:

r1:0x7f621582d010
r1:Foo(100)
b1:0x7f6215823020,data:200
Foo(100)
r:0x7f621582d010
Foo(200)
b:0x7f6215823020
           

Box,Rc 都是在堆上配置設定的,那麼都可以存在于定義它的範圍之外。

  • Rc是通過引用計數的方式獲得更長的lifetime,這兒r=r1.clone()就把堆上的對象的lifetime增加到了外層block對應的lifetime
  • Box通過轉移owner ship的方式把b1傳遞給了b進而讓Box指向的堆上的資源沒有被釋放。

在這種情況下都可以通過不同的方式實作lifetime的擴張,但還是有差別的:

  • Box方式轉移了owner ship之後就不能再通路原來的b1了,也就是隻能一個指針指向Box在堆上配置設定的資源
  • Rc通過增加引用計數的方式實作了lifetime的擴張,并且r1還是繼續使用的