Rust Lang Book Ch.15 Smart Pointers

Smart Pointer也是指向内存中一块数据的指针的一种,但是它提供了比指针更为强大的功能。Smart Pointer与引用的区别是,引用只是借用了对那个的数据,但是smart pointer有时会直接拥有对应的数据。

String和Vec<T>都可以算作是Smart Pointer,都有metadata和特有的功能。Smart Pointer一般是用struct实现的,但是与Struct不同的是,Smart Pointer一般实现了Deref和Drop两种traits。Deref特性主要定义smart pointer在接受dereference operator*时的行为。Drop则是对应的示例出了作用域时应当执行的动作。

Box<T>

允许用户在heap上而不是在stack上存储变量,在stack中存储的只有指向heap的指针本身。

fn main() {
    let b = Box::new(5);
    println!("b = {}", b);
}

  

书中建议在如下场景中使用Box<T>:

1. 当变量对应的类型具体所占空间在编译时不可知,可是编程上下文要求使用一个确定大小的变量时

2. 当有一大块数据需要转移ownership,同时又要确保数据不会被拷贝传值

3.  更关心变量实现了某个trait,而不是变量的类型本身

第一种场景:

Recursive type的域里包含对相同类型的另一个变量,所以编译器无法确定到底这个类型应该占多少空间,通过使用Box(或者Rc,引用&),就能用占空间大小已知(usize)的指针解决这个问题。

enum List {
^^^^^^^^^ recursive type has infinite size
//Rust是用占空间最大的那个pattern作为enum List的占空间大小的
    Cons(i32, List),
    Nil,
}

  

以下代码是一个简单的Recursive Type的定义和实例化示例:

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

  

Deref trait

Deref特性允许程序员自定义dereference operator*作用时的行为。

fn main() {
    let x = 5;
    let y = Box::new(x);

    assert_eq!(5, x);
    assert_eq!(5, *y);
}

  

自定义类型实现Deref特性,当调用dereference operator时,就会先调用deref()方法,然后再进行一次dereference。要加一个plain dereference,而不是直接返回内部值的原因是为了避免move ownership。

use std::ops::Deref;

struct MyBox<T>(T);

impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x)
    }
}
impl<T> Deref for MyBox<T> {
    type Target = T;
//定义Deref特性的相关的那个类型
    fn deref(&self) -> &T {
        &self.0
    }
//let y: MyBox<T>当调用*y的时候,实际上是调用*(y.deref())
}

  

Deref Coercion

对于实现了Deref特性的类型,Deref coercion将对这个类型的引用转化为另外一种类型的引用,例如&String相当于&str,因为String实现了Deref特性,而且String::deref()返回了&str。当我们要把A类型实例的引用传到只兼容B类型引用的函数中时,Rust编译器会自动调用一系列deref方法,直到A类型实例的引用通过dereference operator称为对B类型的引用,兼容函数。

fn hello(name: &str) {
    println!("Hello, {}!", name);
}
fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);//dereference coercion
}

  

对于Mutablility,Deref Coercion用以下策略:

首先,为了获取可变引用,类型需要实现DerefMut特性。

然后,Deref Coercion会按照如下规则进行转换:

&T或者&mut T转换为&U,会调用T: Deref<Target=U>中的deref

&mut T转化为&mut U,则需要T: DerefMut<Target=U>

 

the Drop Trait

Drop常与Smart Pointer搭配,以在Smart Pointer释放时释放申请的其他资源。

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}
//The result of cargo run
//这里drop的顺序是create的逆序
//CustomSmartPointers created.
//Dropping CustomSmartPointer with data `other stuff`!
//Dropping CustomSmartPointer with data `my stuff`!

 

手动drop时,不能直接调用Drop特性的drop方法,必须调用std::mem::drop这个方法。 

fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
//不能是c.drop()
    println!("CustomSmartPointer dropped before the end of main.");
}

  

Rc<T>

Rc,即Reference Counted Smart Pointer,计数的引用会记住当前的引用的个数,并且要求被引对象一直保持有效,除非没有引用与之相关。

由于Rust的Ownership机制,指向同一个变量且包含该变量的Smart Pointer是无法实现的,但是有了Rc就能够实现。

 

 

 以下代码无法通过编译:

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use crate::List::{Cons, Nil};

fn main() {
   let a = Cons(5, Box::new(Cons(10, Box::new(Nil))));
       - move occurs because `a` has type `List`, which does not implement the `Copy` trait
   let b = Cons(3, Box::new(a));
                            - value moved here
   let c = Cons(4, Box::new(a));
                            ^ value used here after move
}

  

使用Rc重构:

enum List {
    Cons(i32, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::rc::Rc;

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    let b = Cons(3, Rc::clone(&a));
//此时因为a是Rc<T>,也可以调用a.clone(),但是,Rust传统上习惯用Rc::clone(&a),因为这样做更能明显区分 let c = Cons(4, Rc::clone(&a)); }

  

调用Rc::clone后,Rc的计数会增加,但是实际上不会发生数据的拷贝,因此不会有很大的额外开销。

用Rc::strong_count(&a)能够获得当前的引用计数。

fn main() {
    let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));
    let b = Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}

 输出为

count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2

  

当然,Rc<T>不允许多个mutable reference,这些都只能是immutable reference。此外,Rc只能用在单线程环境中。

 

the Interior Mutability Pattern

内在的可变性指一个变量在自己的方法中可以更改自己的域但是对外界来说会认为是不变的,Rust提供的一种即使在存在其他只读引用时,也能修改数据的一种设计模式。要使用这个设计模式,需要用unsafe来绕过。这些unsafe代码会被包在safe API中,以保持外层的类型仍然是只读不变的形式存在。

 

RefCell<T>

the Borrowing rules,即变量所有权的借用规则是:

1. 最多只能存在以下两种情况之一:a)有一个可变引用 b)有若干只读引用

2. 引用必须要一直保持有效

通常编译器会保证the borrowing rule成立,但是用RefCell,就能绕过编译器,在运行时保证the borrowing rule成立。即使使用不可变的RefCell<T>,可以修改内部的值。如果the borrowing rule出现了问题,程序会直接panic。此外,RefCell只能在单线程中使用,多线程会panic。

RefCell<T>::borrow()能够获取只读的引用Ref<T>,borrow_mut()能够获取可修改的引用RefMut<T>,二者都可以像正常引用一样使用。RefCell会记录当前有效的Ref和RefMut数目,以在运行过程中确保borrowing rule成立。当然,这些逻辑会带来一点运行代价。

 

那么Interior Mutablility何时有用呢?

书中给出了一种应用:mocking。在这个场景中,被测试的模块认为要调用的Messenger是只读的,调用的send方法也不会改动Messenger本身的状态,但是MockMessenger却使用了Interior Mutability规则来记录返回的值以确定是否对。

被测试的模块:

#[cfg(test)]
mod tests {
    use super::*;

    struct MockMessenger {
        sent_messages: Vec<String>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: vec![],
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.push(String::from(message));
        }
    }

    #[test]
    fn it_sends_an_over_75_percent_warning_message() {
        let mock_messenger = MockMessenger::new();
        let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

        limit_tracker.set_value(80);

        assert_eq!(mock_messenger.sent_messages.len(), 1);
    }
}

  

MockMessenger

#[cfg(test)]
mod tests {
    use super::*;
    use std::cell::RefCell;

    struct MockMessenger {
        sent_messages: RefCell<Vec<String>>,
    }

    impl MockMessenger {
        fn new() -> MockMessenger {
            MockMessenger {
                sent_messages: RefCell::new(vec![]),
            }
        }
    }

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            self.sent_messages.borrow_mut().push(String::from(message));
//获取了mutable reference } } #[test] fn it_sends_an_over_75_percent_warning_message() { // --snip-- assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
//borrow()获取了immutable reference } }

  

如果违反了borrowing rule,同时获取两个mutable reference,会发生panic:

    impl Messenger for MockMessenger {
        fn send(&self, message: &str) {
            let mut one_borrow = self.sent_messages.borrow_mut();
            let mut two_borrow = self.sent_messages.borrow_mut();

            one_borrow.push(String::from(message));
            two_borrow.push(String::from(message));
        }
    }


$ cargo test
   Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
    Finished test [unoptimized + debuginfo] target(s) in 0.91s
     Running target/debug/deps/limit_tracker-d1b2637139dca6ca

running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... FAILED

failures:

---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'main' panicked at 'already borrowed: BorrowMutError', src/libcore/result.rs:1188:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.


failures:
    tests::it_sends_an_over_75_percent_warning_message

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out

error: test failed, to rerun pass '--lib'

  

Multiple Owners of Mutable Data

结合RefCell和Rc,就能够在保证borrowing rule的前提下让多个其他变量拥有当前变量,且通过其他变量的方法都能修改当前变量。但是在修改时,一定要记得不要继续存着/准备用只读的引用,把这些引用释放掉。

#[derive(Debug)]
enum List {
    Cons(Rc<RefCell<i32>>, Rc<List>),
    Nil,
}

use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}

  

如果Rc<T>和RefCell<T>一起用,那就可以改变Rc<T>的指向,导致生成环。生成环的时候,正常情况下drop尝试找到一个strong_count = 1(初始1代表这个变量本身,drop后就是0),入度为0的节点,然后开始减少子List中的strong_count,并且在子list的count为0时直接释放子list,所以环中全部节点都不能释放,导致内存泄漏。不过因为在drop时是直接判断strong_count是否为1,所以整个drop没被激活,进而不会导致不断在环上遍历,也即不会死循环。如果是tail这种想要找到环的终点的函数,就会导致循环调用直到stack overflow。

下面的代码形成了a->b->a这样的小环,

fn main() {
    let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));

    println!("a initial rc count = {}", Rc::strong_count(&a));//1
    println!("a next item = {:?}", a.tail());//Some(RefCell { value: Nil })

    let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));

    println!("a rc count after b creation = {}", Rc::strong_count(&a));//2
    println!("b initial rc count = {}", Rc::strong_count(&b));//1
    println!("b next item = {:?}", b.tail());//Some(RefCell { value: Cons(5, RefCell { value: Nil }) })

    if let Some(link) = a.tail() {
        *link.borrow_mut() = Rc::clone(&b);
    }

    println!("b rc count after changing a = {}", Rc::strong_count(&b));//2
    println!("a rc count after changing a = {}", Rc::strong_count(&a));//2

    // Uncomment the next line to see that we have a cycle;
    // it will overflow the stack
    // println!("a next item = {:?}", a.tail());
}

  

RefCell<T> + Weak<T>

Rc<T>::downgrade会返回一个Weak<T>,同时weak_count+1,但是weak_count和strong_count不同,weak_count是否为0都不影响drop。Weak<T>不能保证对应的变量是否还存在,所以需要用Weak<T>::upgrade再去得到一个Option<Rc<T>>,如果已经无效了,那么就会返回None。

use std::cell::RefCell;
use std::rc::{Rc, Weak};

#[derive(Debug)]
struct Node {
    value: i32,
    parent: RefCell<Weak<Node>>,
    children: RefCell<Vec<Rc<Node>>>,
}

  

fn main() {
    let leaf = Rc::new(Node {
        value: 3,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![]),
    });

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());

    let branch = Rc::new(Node {
        value: 5,
        parent: RefCell::new(Weak::new()),
        children: RefCell::new(vec![Rc::clone(&leaf)]),
    });

    *leaf.parent.borrow_mut() = Rc::downgrade(&branch);

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
}
posted @ 2020-10-27 17:10  雪溯  阅读(177)  评论(0编辑  收藏  举报