Skip to the content.

rust 的 smart pointers

15.5

这个回答是一个不错的总结: https://stackoverflow.com/questions/45674479/need-holistic-explanation-about-rusts-cell-and-reference-counted-types

A common way to use RefCell<T> is in combination with Rc<T> 一个可以存在多个 owner 同时修改了。

然后继续理解下,为什么 loop 的会导致 memory leak

strong 和 weak 的作用就可以了

有趣的

MutexGuard<'a, T>
C++                    Rust

unique_ptr<T>     ≈     Box<T>
shared_ptr<T>     ≈     Rc<T>
shared_ptr<T>     ≈     Arc<T>  // thread safe
weak_ptr<T>       ≈     Weak<T>

Box 的本质

stack                       heap
─────                       ────

p
┌─────────┐
│ ptr ────────────────────>│ 123 │
└─────────┘                └─────┘

String 本质

stack

s
┌────────────┐
│ ptr        │──────┐
│ len = 5    │      │
│ cap = 5    │      │
└────────────┘      │
                    ▼
heap              hello

rust : weak pointr

我想引用这个对象,但不想延长它的生命周期

问题

假设两个对象互相强引用:

struct A {
    b: Rc<B>,
}

struct B {
    a: Rc<A>,
}

关系变成:

A ──Rc──> B
^         │
│         │
└──Rc─────┘

即使外部已经不再使用 AB,它们内部的强引用还互相撑着:

A strong_count = 1
B strong_count = 1

所以引用计数永远不会降到 0:

A 不能 drop
B 不能 drop

最终就是内存泄漏。

Weak<T> 的关键点是:

Weak 可以指向对象,但不增加 strong reference count

例如改成:

A ──Rc──> B
^         │
│         │
└─Weak────┘

那么外部强引用消失后:

A strong_count -> 0

于是 A 可以销毁。

随后:

B strong_count -> 0

B 也可以销毁。

所以 Weak 打破了 ownership cycle。


再看一个特别典型的树结构。

父节点拥有子节点,这是自然的:

Parent
  │
  │ Rc
  ▼
Child

但子节点通常也希望能访问父节点:

Parent
  │
  ▼
Child
  │
  └────> Parent

如果两边都是 Rc

Parent ──Rc──> Child
Parent <──Rc── Child

就是引用环。

正确方式通常是:

Parent ──Rc──> Child
Parent <─Weak─ Child

语义也非常合理:

父节点拥有 child,所以 child 的生命周期由 parent 保证。 child 只是“知道自己的 parent 是谁”,它不应该因此拥有 parent。

这就是 Weak 背后的 ownership 语义。

  1. 比如缓存:
Cache
  │
  └── Weak<Object>

缓存希望:

“如果对象还存在,我就复用;如果没人用了,它应该正常销毁。”

如果缓存存的是:

  1. 另一个常见场景是 observer/listener。

比如:

Window
 ├── Button
 ├── Label
 └── EventListener

listener 想引用 Window,但它不应该:

listener keeps Window alive forever

所以这种 back-reference 往往适合 Weak。

解决办法

Rust 中通常是:

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

struct Node {
    parent: Weak<Node>,
}

但 Weak 有一个重要性质:

Weak<T>

不能直接访问 T

因为 Weak 指向的对象可能已经死了。

你必须:

weak.upgrade()

得到:

Option<Rc<T>>

例如:

if let Some(parent) = weak_parent.upgrade() {
    println!("parent still alive");
} else {
    println!("parent already dropped");
}

为什么是 Option

因为:

Weak
 │
 ▼
object

这个 object 不一定还存在。

所以 Weak 的语义其实是:

“如果这个对象还活着,我可以临时获得一个 strong reference。”


可以把 RcWeak 理解成两个计数。

假设:

let a = Rc::new(Foo {});
let w = Rc::downgrade(&a);

内部大概:

Rc control block
┌──────────────────┐
│ strong = 1       │
│ weak   = 1       │
│ Foo {...}        │
└──────────────────┘

Rc drop 后:

strong: 1 → 0

此时:

Foo 被 drop

但是 control block 还不能马上释放,因为还有:

Weak

需要知道对象已经死了。

于是大致变成:

control block
┌──────────────────┐
│ strong = 0       │
│ weak   = 1       │
│ Foo: dead        │
└──────────────────┘

此时:

w.upgrade()

得到:

None

等最后一个 Weak 也 drop:

weak = 0

control block 才完全释放。

所以可以区分:

strong count
    ↓
决定 T 是否存活

weak count
    ↓
决定 control block 是否还需要存在

这个区别非常重要。

总结

从 ownership 的角度,三者可以这么理解:

Box<T>
    ↓
唯一 owner

Rc<T> / Arc<T>
    ↓
共享 owner

Weak<T>
    ↓
non-owner observer

本站所有文章转发 CSDN 将按侵权追究法律责任,其它情况随意。