Rust - Basic - 14 - Smart Pointers

Intelligente Zeiger

Verständnis

Typen, die Deref und Drop implementieren, können als intelligente Zeiger dienen.

Box

Drei typische Einsatzfälle für Box:

  • Wenn die Größe eines Typs zur Laufzeit unbekannt ist.
  • Wenn Daten groß sind und Kopierkosten beim Übertragen des Besitzes vermieden werden sollen.
  • Wenn man einen Wert besitzen, aber nur einen bestimmten Trait beachten möchte.

recursive type Rust ,

// 以下代码会编译失败
enum List {
    Cons(i32, List),
    Nil,
}

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

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

1

Mit Box kann der Compiler die Speichergröße des Typs ableiten.

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))))));
}

2

Deref und Drop

,, Rust ( Deref trait deref )

Deref coercion happens automatically when we pass a reference to a particular type’s value as an argument to a function or method that doesn’t match the parameter type in the function or method definition.

Drop trait drop , value goes out of scope (FILO)

drop

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}
// 先调用 d 的 drop 方法,再调用 c 的 drop 方法
fn main() {
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };
    println!("CustomSmartPointers created.");
}

// drop 不允许被主动调用,以下代码会编译失败
fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    c.drop();
    println!("CustomSmartPointer dropped before the end of main.");
}
// 可以通过 std::mem:drop 间接触发 drop 的调用
fn main() {
    let c = CustomSmartPointer {
        data: String::from("some data"),
    };
    println!("CustomSmartPointer created.");
    drop(c);
    println!("CustomSmartPointer dropped before the end of main.");
}

Rc

Referenzzählung, geeignet zur Implementierung von Graphen.

// 基础写法
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));  // 使用 clone 实现浅拷贝
    let c = Cons(4, Rc::clone(&a));
}

// 使用 strong_count 获取当前变量被引用的数量
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));
}

RefCell

Box RefCell ,RefCell runtime immutable or mutable borrows checked

, panic

 Box<T>, Rc<T>, or RefCell<T> :

  • Rc<T> owner,Box<T> RefCell<T> owner

    Rc<T> enables multiple owners of the same data; Box<T> and RefCell<T> have single owners.

  • Box<T>  immutable or mutable borrows checked; Rc<T>  immutable borrows checked; RefCell<T>  immutable or mutable borrows checked

    Box<T> allows immutable or mutable borrows checked at compile time; Rc<T> allows only immutable borrows checked at compile time; RefCell<T> allows immutable or mutable borrows checked at runtime.

  • RefCell<T>  mutable borrows checked, RefCell<T> , RefCell<T>

    Because RefCell<T> allows mutable borrows checked at runtime, you can mutate the value inside the RefCell<T> even when the RefCell<T> is immutable.

// 基础写法 - 和 Rc 配合使用
#[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);
}

Weak

Strong<T>

Rc::downgrade, Weak<T> , weak_count 1

strong_count 0 ,weak_count 0 , ref

upgrade ( Option )

// 可以通过该例直观感受弱引用、强引用使用上的区别
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 strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );

    {
        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!(
            "branch strong = {}, weak = {}",
            Rc::strong_count(&branch),
            Rc::weak_count(&branch),
        );

        println!(
            "leaf strong = {}, weak = {}",
            Rc::strong_count(&leaf),
            Rc::weak_count(&leaf),
        );
    }

    println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
    println!(
        "leaf strong = {}, weak = {}",
        Rc::strong_count(&leaf),
        Rc::weak_count(&leaf),
    );
}
leaf strong = 1, weak = 0
branch strong = 1, weak = 1
leaf strong = 2, weak = 0
leaf parent = None
leaf strong = 1, weak = 0

Quelle

https://doc.rust-lang.org/book/ch15-00-smart-pointers.html

…

Intelligente Zeiger besitzen zusätzliche Metadaten und Fähigkeiten gegenüber gewöhnlichen Referenzen. Box<T> speichert Daten auf dem Heap, Deref ermöglicht die Verwendung wie eine Referenz, und Drop bestimmt, was beim Verlassen des Gültigkeitsbereichs geschieht. Rc<T> zählt gemeinsame Besitzer; RefCell<T> verschiebt die Prüfung von Borrowing-Regeln von der Kompilierzeit in die Laufzeit.