C++ shared_ptr 的 Rust 等价物是什么?

What is the Rust equivalent of C++'s shared_ptr?

为什么 Rust 中不允许使用此语法:

fn main() {
    let a = String::from("ping");
    let b = a;

    println!("{{{}, {}}}", a, b);
}

当我尝试编译这段代码时,我得到:

error[E0382]: use of moved value: `a`
 --> src/main.rs:5:28
  |
3 |     let b = a;
  |         - value moved here
4 | 
5 |     println!("{{{}, {}}}", a, b);
  |                            ^ value used here after move
  |
  = note: move occurs because `a` has type `std::string::String`, which does not implement the `Copy` trait

其实我们可以简单的做一个引用——在运行时是不一样的:

fn main() {
    let a = String::from("ping");
    let b = &a;

    println!("{{{}, {}}}", a, b);
}

有效:

{ping, ping}

根据the Rust Book,这是为了避免双重错误,因为 Rust 的变量是通过引用而不是通过值来复制的。 Rust 只会使第一个对象无效并使其无法使用...

我们必须做这样的事情:

我喜欢通过引用复制的想法,但为什么自动使第一个无效?

应该可以用不同的方法避免双重释放。例如,C++ 已经有一个很棒的工具来允许多次自由调用...... shared_ptr 只有在没有其他指针指向该对象时才调用自由 - 它似乎与我们实际做的非常相似,与shared_ptr 有一个计数器的区别。

例如,我们可以在编译期间统计每个对象的引用次数,只有当最后一个引用超出范围时才调用free

但是 Rust 是一门年轻的语言;也许他们没有时间实施类似的东西? Rust 是否计划允许对对象的第二次引用而不会使第一个引用无效,或者我们应该养成只使用引用的引用的习惯?

Rc or Arcshared_ptr 的替代品。选择哪种取决于共享数据所需的线程安全级别; Rc 用于非线程情况,Arc 用于需要线程的情况:

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("ping"));
    let b = a.clone();

    println!("{{{}, {}}}", a, b);
}

shared_ptr 一样,这 而不是 复制 String 本身。它仅在调用 clone 时在运行时增加引用计数器,并在每个副本超出范围时减少计数器。

shared_ptr 不同,RcArc 具有更好的线程语义。 shared_ptr is semi-thread-safeshared_ptr 的引用计数器本身是线程安全的,但共享数据不是 "magically" 线程安全的。

如果您在线程程序中使用 shared_ptr,您还需要做更多的工作来确保它的安全。在非线程程序中,您正在为一些不需要的线程安全付出代价。

如果您希望允许更改共享值,您还需要切换到运行时借用检查。这是由 CellRefCellMutex 等类型提供的。RefCell 适用于 StringRc:

use std::cell::RefCell;
use std::rc::Rc;

fn main() {
    let a = Rc::new(RefCell::new(String::from("ping")));
    let b = a.clone();

    println!("{{{}, {}}}", a.borrow(), b.borrow());

    a.borrow_mut().push_str("pong");
    println!("{{{}, {}}}", a.borrow(), b.borrow());
}

we can count the number of references to each object during the compilation time and call free only when the last reference goes out of the scope.

这几乎就是 Rust 对引用所做的。它实际上不使用计数器,但它只允许您使用对值的引用,同时保证该值保持在相同的内存地址。

C++ 的 shared_ptr 不会 在编译时执行此操作。 shared_ptrRcArc 都是维护计数器的运行时构造。

Is it possible to make a reference to the object without invalidate the first reference?

这正是 Rust 对引用所做的,以及您已经完成的:

fn main() {
    let a = String::from("ping");
    let b = &a;

    println!("{{{}, {}}}", a, b);
}

更好的是,一旦 a 不再有效,编译器将阻止您使用 b

because Rust's variables are copied by reference instead of by value

这不是真的。当您分配一个值时,该值的所有权将转移给新变量。从语义上讲,变量的内存地址已更改,因此读取该地址可能会导致内存不安全。

should we take the habit to only work with a reference

是的,尽可能使用引用是最惯用的选择。这些需要零运行时开销,编译器会告诉您错误,而不是在运行时遇到它们。

肯定会有 RcArc 有用的时候。循环数据结构通常需要它们。如果您无法获得简单的工作参考,您不应该因为使用它们而感到难过。

with a reference of a reference?

这有点不利,因为额外的间接寻址是不幸的。如果你真的需要,你可以减少它。如果不需要修改字符串,可以改用 Rc<str> 代替:

use std::rc::Rc;

fn main() {
    let a: Rc<str> = Rc::from("ping");
    let b = a.clone();

    println!("{{{}, {}}}", a, b);
}

如果有时需要保留修改String的能力,也可以将&Rc<T>显式转换为&T:

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("ping"));
    let b = a.clone();

    let a_s: &str = &*a;
    let b_s: &str = &*b;

    println!("{{{}, {}}}", a_s, b_s);
}

另请参阅:

Maybe we can simply count the number of references to each object during the compile time and call free only when the last reference goes out of the scope.

你走对了!这就是 Rc 的用途。它是一种非常类似于 C++ 中的 std::shared_ptr 的智能指针类型。它仅在最后一个指针实例超出范围后释放内存:

use std::rc::Rc;

fn main() {
    let a = Rc::new(String::from("ping"));

    // clone() here does not copy the string; it creates another pointer
    // and increments the reference count
    let b = a.clone();

    println!("{{{}, {}}}", *a, *b);
}

由于您只能对 Rc 的内容进行不可变访问(毕竟它是共享的,并且 Rust 中禁止共享可变性),因此您需要内部可变性才能更改其内容,通过 Cell or RefCell:

use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let a = Rc::new(RefCell::new(String::from("Hello")));
    let b = a.clone();

    a.borrow_mut() += ", World!";

    println!("{}", *b); // Prints "Hello, World!"
}

但大多数时候,您根本不需要使用 Rc(或其线程安全兄弟 Arc)。 Rust 的所有权模型主要允许您通过在一个地方声明 String 实例并在其他地方使用对它的引用来避免引用计数的开销,就像您在第二个片段中所做的那样。尝试专注于此并仅在确实需要时才使用 Rc,例如当您实现类似图形的结构时。