繁体   English   中英

rust 如何有多个可变引用分配给 object 的堆栈?

[英]rust how to have multiple mutable references to a stack allocated object?

假设我们有这个 C 代码:

typedef struct A { int i; } A;
typedef struct B { A* a; } B;
typedef struct C { A* a; } C;

int main(void)
{
  A a = { .i = 42 };
  B b = { .a = &a };
  C c = { .a = &a };
}

在这种情况下,A 是堆栈分配的,B 和 C 指向 A 所在的堆栈分配 memory。

我需要在 rust 中做完全相同的事情,但每次我尝试创建多个可变引用时都会抱怨。

不得不与语言对抗以完成如此基本的事情有点令人沮丧。

这可能不适用于您在评论中指出的具体情况,但在 Rust 中解决此问题的一般方法是使用RefCell 此类型允许您从&RefCell<T>获取&mut T 例如:

use std::cell::RefCell;

struct A(pub RefCell<i32>);
struct B<'a>(pub &'a RefCell<i32>);

fn main() {
    let a = A(RefCell::new(0));
    let b = B(&a.0);
    let c = B(&a.0);
    
    *b.0.borrow_mut() = 1;
    println!("{}", c.0.borrow());
    
    *c.0.borrow_mut() = 2;
    println!("{}", b.0.borrow());
}

请注意, RefCell以借用计数的形式存在开销,这是在运行时强制执行 Rust 的别名规则所必需的(如果可变借用与任何其他借用同时存在,它将出现恐慌)。

如果底层类型是Copy并且您不需要对内部值的引用,那么您可以使用Cell ,它没有任何运行时开销,因为每个操作都会完全检索或替换包含的值:

use std::cell::Cell;

struct A(pub Cell<i32>);
struct B<'a>(pub &'a Cell<i32>);

fn main() {
    let a = A(Cell::new(0));
    let b = B(&a.0);
    let c = B(&a.0);
    
    b.0.set(1);
    println!("{}", c.0.get());
    
    c.0.set(2);
    println!("{}", b.0.get());
}

请注意, Cell#[repr(transparent)] ,它在系统编程中特别有用,因为它允许在不同类型之间进行某些类型的零成本转换。

不得不与语言对抗以完成如此基本的事情有点令人沮丧。

它不像你想象的那么基本。 Rust 的主要前提是零未定义行为,并且几乎不可能同时拥有两个可变引用同时维护该保证。 您将如何确保通过多线程不会意外获得竞争条件? 这已经是可用于恶意手段的未定义行为。

学习 Rust 并不容易,如果您来自不同的语言,则尤其困难,因为许多编程范例根本不适用于 Rust。 但我可以向你保证,一旦你了解了如何以不同的方式构造代码,它实际上将成为一件好事,因为 Rust 迫使程序员远离有问题的模式,或者看起来不错但需要再看一眼才能理解实际错误的模式跟他们。 C/C++ 错误通常是非常微妙的,并且是由一些奇怪的极端情况引起的,在 Rust 中编程一段时间后,确保这些极端情况根本不存在是非常值得的。

但是回到你的问题。

这里有两个语言概念需要结合起来才能实现你想要做的事情。

这一次,借用检查器强制您一次只有一个对特定片段数据的可变引用。 这意味着,如果您确实想从多个地方修改它,您将不得不利用一个称为内部可变性的概念。 根据您的用例,有几种方法可以创建内部可变性:

  • Cell - 单线程,用于可以通过复制来替换的原始类型。 这是一个零成本的抽象。
  • RefCell - 单线程,用于需要可变引用而不是通过替换来更新的更复杂的类型。 检查它是否已被借用的最小开销。
  • Atomic - 多线程,用于原始类型。 在大多数情况下,零成本抽象(在 x86-64 上,直到 u64/i64 的所有内容都已经是开箱即用的原子,需要零开销)
  • Mutex - 类似RefCell ,但用于多个线程。 由于活动的内部锁管理,开销更大。

因此,根据您的用例,您需要选择正确的用例。 在您的情况下,如果您的数据确实是int ,我会使用 go 和CellAtomic

其次,首先存在如何获取对 object 的多个(不可变)引用的问题。

马上,我想告诉你:不要过早地使用原始指针。 原始指针和unsafe绕过借用检查器并使 Rust 作为语言毫无意义。 99.9% 的问题在不使用原始指针的情况下运行良好且性能良好,因此仅在绝对没有替代方案存在的情况下使用它们。

也就是说,共享数据的一般方法有以下三种:

  • &A - 正常参考。 虽然引用存在,但引用的 object 无法移动或删除。 所以这可能不是你想要的。
  • Rc<A> - 单线程引用计数器。 非常轻巧,所以不用担心开销。 访问数据是零成本抽象,仅当您复制/删除实际的Rc object 时才会产生额外成本。 理论上移动Rc object应该是免费的,因为这不会改变引用计数。
  • Arc<A> - 多线程引用计数器。 Rc一样,实际访问是零成本,但复制/删除Arc object 本身的成本比Rc高一点。 移动Arc object 理论上应该是免费的,因为这不会改变引用计数。

因此,假设您有一个单线程程序并且问题与您提出的完全一样,我会这样做:

use std::{cell::Cell, rc::Rc};

struct A {
    i: Cell<i32>,
}
struct B {
    a: Rc<A>,
}
struct C {
    a: Rc<A>,
}

fn main() {
    let a = Rc::new(A { i: Cell::new(42) });
    let b = B { a: Rc::clone(&a) };
    let c = C { a: Rc::clone(&a) };

    b.a.i.set(69);
    c.a.i.set(c.a.i.get() + 2);
    println!("{}", a.i.get());
}
71

但当然所有其他组合,如Rc + AtomicArc + AtomicArc + Mutex等也是可行的。 这取决于您的用例。

如果您的bc对象的寿命可证明比a短(意思是,如果它们仅存在于几行代码并且不会移动到其他任何地方),那么当然使用引用而不是Rc Rc和直接引用之间最大的性能差异是Rc内部的 object 存在于堆上,而不是栈上,因此它相当于在 C++ 中调用一次new / delete

因此,作为参考,如果您的数据共享允许 object 存在于堆栈中,就像在我们的示例中一样,那么代码将如下所示:

use std::cell::Cell;

struct A {
    i: Cell<i32>,
}
struct B<'a> {
    a: &'a A,
}
struct C<'a> {
    a: &'a A,
}

fn main() {
    let a = A { i: Cell::new(42) };
    let b = B { a: &a };
    let c = C { a: &a };

    b.a.i.set(69);
    c.a.i.set(c.a.i.get() + 2);
    println!("{}", a.i.get());
}
71

请注意,引用是零成本的,而Cell也是零成本的,因此此代码将执行 100% 的操作,就像您使用原始指针一样; 不同的是借用检查器现在可以证明这不会导致未定义的行为。

为了证明这是多么的零成本,请查看上面示例的组件 output 编译器设法将整个代码优化为:

fn main() {
    println!("{}", 71);
}

请注意,在您的 C 示例中,没有什么可以阻止您将b object 复制到其他地方,而a离开 scope 并被破坏。 这将导致未定义的行为,并会被 Rust 中的借用检查器阻止,这就是结构BC携带生命周期'a以跟踪它们借用A的事实的原因。

最后,我想谈谈unsafe的代码。 是的,如果您与C接口或编写需要直接访问 memory 的低级驱动程序,那么unsafe是绝对重要的。 也就是说,了解如何处理unsafe以保持 Rust 的安全保证是很重要的。 否则,使用 Rust 真的没有任何意义。 不要只是为了方便而使用unsafe来简单地否决借用检查器,而是要确保由此产生的unsafe使用是合理的。 在使用unsafe关键字之前,请阅读这篇关于健全性的文章。

我希望这能让您了解在 Rust 中编程需要什么样的思维,并希望它没有吓到您太多。 给它一个机会; 虽然学习曲线相当陡峭,特别是对于具有丰富其他语言先验知识的程序员来说,但它可能是非常有益的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM