首页 文章

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

提问于
浏览
1

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

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

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

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

错误[E0382]:使用移动值:a

  • src / main.rs:5:28
    |
    3 |设b = a;
    | - Value 移到这里
    4 |
    5 | println!(“{{{},{}}}”,a,b);
    | ^移动后使用的值
    |
    =注意:移动发生是因为a的类型为std :: string :: String,它没有实现Copy特性

实际上,我们可以简单地创建一个引用 - 在运行时它是不一样的:

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

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

它有效:

{ping,ping}

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

enter image description here

我们必须做这样的事情:

enter image description here

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

应该可以通过不同的方法避免双重自由 . 例如,C已经有了一个很好的工具来允许多个空闲调用...只有当没有其他指针指向对象时,shared_ptr调用才会自由 - 它看起来与我们实际做的非常相似,区别在于 shared_ptr 一个柜台 .

例如,我们可以在编译期间计算每个对象的引用数,并仅在最后一个引用超出范围时调用 free .

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

2 回答

  • 10

    RcArcshared_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 一样, not 复制 String 本身 . 它只会在调用 clone 时在运行时增加引用计数器,并在每个副本超出范围时减少计数器 .

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

    如果在线程程序中使用 shared_ptr ,仍然需要做更多的工作来确保它不需要's safe. In a non-threaded program, you are paying for some thread-safety you don' .

    如果您希望允许变更共享值,则还需要切换到运行时借用检查 . 这是由 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());
    }
    

    我们可以在编译时计算每个对象的引用数,并且仅在最后一个引用超出范围时才调用 .

    's almost exactly what Rust does with references. It doesn'实际上使用了一个计数器,但它只允许您使用对值的引用,同时保证该值保持在同一个内存地址 .

    C的 shared_ptr 在编译时做了 not 这样做 . shared_ptrRcArc 都是维护计数器的运行时构造 .

    是否可以在不使第一个引用无效的情况下引用对象?

    这正是Rust对引用所做的,以及你已经做过的事情:

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

    更好的是,只要 a 不再有效,编译器就会阻止您使用 b .

    因为Rust的变量是通过引用而不是按值复制的

    这不是真的 . 分配值时,值的所有权将传输到新变量 . 从语义上讲,变量的内存地址已经改变,因此读取该地址可能会导致内存不安全 .

    我们应该养成只参与参考的习惯

    是的,尽可能使用参考是最惯用的选择 . 这些需要零运行时开销,编译器会告诉您有关错误的信息,而不是在运行时遇到错误 .

    肯定有 RcArc 有用的时候 . 通常它们是循环数据结构所必需的 . 如果你不能得到明确的工作参考,你不应该对使用它们感到不舒服 .

    参考参考?

    这有点不利,因为额外的间接是不幸的 . 如果你真的需要,你可以减少它 . 如果您不需要修改字符串,则可以切换到 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);
    }
    

    也可以看看:

  • 4

    也许我们可以在编译期间简单地计算每个对象的引用数,并且只有在最后一个引用超出范围时才调用 .

    你走在正确的轨道上!这就是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 's contents (it'共享的不可变访问权限,并且Rust中禁止共享可变性,因此您需要内部可变性才能更改其内容,通过CellRefCell实现:

    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 ,例如当您实现类似图形的结构时 .

相关问题