将C API绑定(bind)到Rust的一部分,我有一个可变的引用ph: &mut Ph,一个struct struct EnsureValidContext<'a> { ph: &'a mut Ph }和一些方法:

impl Ph {
    pub fn print(&mut self, s: &str) {
        /*...*/
    }
    pub fn with_context<F, R>(&mut self, ctx: &Context, f: F) -> Result<R, InvalidContextError>
    where
        F: Fn(EnsureValidContext) -> R,
    {
        /*...*/
    }
    /* some others */
}

impl<'a> EnsureValidContext<'a> {
    pub fn print(&mut self, s: &str) {
        self.ph.print(s)
    }
    pub fn close(self) {}
    /* some others */
}

我不控制这些。我只能使用这些。

现在,如果您希望编译器强制您考虑性能(以及必须在性能和所需行为之间进行权衡,上下文验证非常昂贵),则闭包API很好。但是,假设您只是不关心它,而是希望它能正常工作。

我当时正在考虑制作一个可以为您处理的 wrapper :
enum ValidPh<'a> {
    Ph(&'a mut Ph),
    Valid(*mut Ph, EnsureValidContext<'a>),
    Poisoned,
}

impl<'a> ValidPh<'a> {
    pub fn print(&mut self) {
        /* whatever the case, just call .print() on the inner object */
    }
    pub fn set_context(&mut self, ctx: &Context) {
        /*...*/
    }
    pub fn close(&mut self) {
        /*...*/
    }
    /* some others */
}

这可以通过在必要时检查我们是Ph还是Valid来实现,如果我们是Ph,则可以通过以下方式升级到Valid:
fn upgrade(&mut self) {
    if let Ph(_) = self { // don't call mem::replace unless we need to
        if let Ph(ph) = mem::replace(self, Poisoned) {
            let ptr = ph as *mut _;
            let evc = ph.with_context(ph.get_context(), |evc| evc);
            *self = Valid(ptr, evc);
        }
    }
}

每个方法的降级方法不同,因为它必须调用目标方法,但这是close的示例:
pub fn close(&mut self) {
    if let Valid(_, _) = self {
        /* ok */
    } else {
        self.upgrade()
    }
    if let Valid(ptr, evc) = mem::replace(self, Invalid) {
        evc.close(); // consume the evc, dropping the borrow.

        // we can now use our original borrow, but since we don't have it anymore, bring it back using our trusty ptr
        *self = unsafe { Ph(&mut *ptr) };
    } else {
        // this can only happen due to a bug in our code
        unreachable!();
    }
}

您可以像这样使用ValidPh:
/* given a &mut vph */
vph.print("hello world!");
if vph.set_context(ctx) {
    vph.print("closing existing context");
    vph.close();
}
vph.print("opening new context");
vph.open("context_name");
vph.print("printing in new context");

没有vph,您将不得不自己摆弄&mut PhEnsureValidContext。尽管Rust编译器做到这一点并不重要(只需遵循错误),但您可能希望让该库为您自动处理它。否则,无论该操作是否会使上下文无效,您最终都可能只为每个操作调用非常昂贵的with_context

请注意,此代码是粗略的伪代码。我还没有编译或测试它。

可能有人争辩说我需要UnsafeCellRefCell或其他一些Cell。但是,由于内部可变性,从读取this来看,UnsafeCell似乎只是一个lang项目-仅在通过&T进行状态突变时才有必要,而在这种情况下,我会一直使用&mut T

但是,我的阅读可能有缺陷。此代码是否调用UB?

(PhEnsureValidContext的完整代码,包括FFI位,可用here。)

最佳答案

退后一步,Rust坚持的保证是:

  • &T是对T的引用,它可能是别名
  • &mut T是对T的引用,它保证不会被别名。

  • 因此,问题的症结在于:保证不被别名意味着什么?

    让我们考虑一个安全的Rust示例:
    struct Foo(u32);
    
    impl Foo {
        fn foo(&mut self) { self.bar(); }
        fn bar(&mut self) { *self.0 += 1; }
    }
    
    fn main() { Foo(0).foo(); }
    

    如果在执行Foo::bar时窥视堆栈,我们将至少看到两个指向Foo的指针:一个在bar中,一个在foo中,并且在堆栈或其他寄存器中可能还有其他副本。

    因此,很明显,存在别名。怎么会!保证不被别名!

    深吸一口气:您当时可以访问多少个别名?

    1 没有混叠的保证不是空间的而是时间的。

    因此,我认为在任何时间点,如果可以访问&mut T,那么就不必访问该实例的其他引用。

    具有原始指针(*mut T)很好,它需要unsafe才能访问;但是,即使不使用它,形成第二引用也可能是安全的,也可能不是安全的,因此我会避免使用它。

    关于rust - 在Rust中使用原始指针进行运行时借用管理是否是未定义的行为?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/49503331/

    10-09 02:26