在C++中,具有如下排列是非常标准的:

// Some object that you want to manipulate
class DataType 
{ 
public: 
    foo() {} 
    bar() {} 
};

// Some class that manipulates a DataType on construct and destruct
// Your typical RAII scope side effect type thing
class DoFooAndBar
{
public:
    DoFooAndBar(DataType& dataType) : m_dataType(dataType)
    { m_dataType.foo(); }

    ~DoFooAndBar() 
    { m_dataType.bar(); }

private:
    DataType& m_dataType
}

我试图在Rust中做类似的事情,但我遇到了DoFooAndBar采用可变引用的问题,随后的代码无法采用不可变的引用.我可以用你典型的Rc::RefCell黑客来处理这个问题,但我想知道是否有一种正确的方法来做这样的事情而不诉诸那个.

以下是我的铁 rust 代码,我尽可能地为这个问题做了一些修改:

/// Symbol table example to for a hypothetical script interpreter
/// Simplified to demonstrate issues with borrowing references
use std::collections::HashMap;

/// For our purposes a frame from is just a string->string map
type SymbolFrame = HashMap<String, String>;

/// Our stack of frames is just a vector of stack frame
/// Not performant, but that's not the point
type SymbolFrameStack = Vec<SymbolFrame>;

/// The SymbolTable type contains a stack of symbol frames
/// and routines for working with them
pub struct SymbolTable
{
    frames: SymbolFrameStack
}
impl SymbolTable
{
    /// Start with an initial stack with one frame
    pub fn new() -> Self { SymbolTable { frames: vec![SymbolFrame::new()] } }

    /// Push and pop frames
    pub fn push(&mut self) { self.frames.push(SymbolFrame::new()); }
    pub fn pop(&mut self) { self.frames.pop(); }

    /// See if a symbol exists by name anywhere in the frames
    pub fn contains(&self, name: &str) -> bool {
        for frame in self.frames.iter() { 
            if frame.contains_key(name) {
                return true;
            }
        }
        return false;
    }
}

/// Push a frame on new(); pop the frame on drop()
pub struct SymbolStacker<'a> { table: &'a mut SymbolTable }
impl<'a> SymbolStacker<'a>
{
    pub fn new(table: &'a mut SymbolTable) -> SymbolStacker {
        table.push();
        SymbolStacker { table }
    }
}
impl<'a> Drop for SymbolStacker<'a> {
    fn drop(&mut self) {
        self.table.pop();
    }
}

#[test]
fn bad_test_symbol_table() {
    // Create our table for testing
    let table = &mut SymbolTable::new();
    {
        // Enter a new scope of code, pushing a new stack frame
        let _stacker1 = SymbolStacker::new(table);
        {
            // ...a lot of other recursive code
            // ...presumably passing the table struct around in some way
            // ...we just try to see if a symbol exists in the table
            assert!(!table.contains("foo"));
        }

        assert!(table.contains("foo"));
    }
}

它在运行测试时生成编译错误:

error[E0502]: cannot borrow `*table` as immutable because it is also borrowed as mutable

我理解这个错误,我只是在寻求关于如何最好地在Rust中做这类事情的建议.

推荐答案

我知道这不是您想要的答案,但我认为这个问题的最佳答案是"用Rust以这种方式编写代码很混乱,因为Rust不是设计成这样使用的."在Rust中使用RAII作用域副作用表明您是一名C++开发人员,试图用Rust编写C++代码,就像用C++垃圾邮件new表示您是用C++编写Java代码的Java开发人员一样.可以肯定的是,在回答这个问题时,我迅速地查看了一些有声誉的库,但在不是绝对必要的地方找不到一个实例.

但是,如果某些数据 struct 真的需要这种东西(低级数据 struct 可能就是这种情况),最常用的方法可能就是使用unsafe{},只需使用原始指针.例如,这就是在标准库中处理文件句柄的方式.但是,如果您仍在学习Ruust,尤其是作为练习,try 重构代码以避免RAII副作用可能是更明智的做法,因为这些副作用通常是不受欢迎的,尤其是对于高级代码.

没有真正安全的方法来做到这一点是有原因的. rust 蚀安全不仅仅是运行时的安全,根据类型系统,即使是std::Process::Exit也应该是安全的,这会导致内存泄漏,即使在真正惯用的C++代码中也是如此.

Rust相关问答推荐

trait 中self 的显式生命周期似乎导致E0499无法在循环中多次borrow * emits 器作为可变的

在HashMap中插入Vacant条目的可变借位问题

如何处理动态 struct 实例化?

rust 蚀生命周期 行为

字段类型为Boxed的 struct 的生存期必须超过static

我如何使用AWS SDK for Rust获取我承担的角色的凭据?

更合理的方法来设计样条线函数器?

装箱特性如何影响传递给它的参数的生命周期 ?(举一个非常具体的例子)

处理带有panic 的 Err 时,匹配臂具有不兼容的类型

Rust 重写函数参数

详尽的匹配模式绑定

Rust 如何将链表推到前面?

如何在 Rust 中显式声明 std::str::Matches<'a, P> ?

Rust typestate 模式:实现多个状态?

push 方法是否取得所有权?

如何刷新 TcpStream

一个函数调用会产生双重borrow 错误,而另一个则不会

为什么 File::read_to_end 缓冲区容量越大越慢?

当我不满足特征界限时会发生什么?

在 RefCell 上borrow