簡體   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