3

作为将 C API 绑定到 Rust 的一部分,我有一个可变引用ph: &mut Ph、一个结构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 非常好。但是,假设您只是不关心它并希望它正常工作。

我正在考虑制作一个为您处理它的包装器:

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 */
}

这将在必要时检查我们是 aPh还是 a Valid,如果我们是 a ,Ph我们将Valid通过以下方式升级到 a :

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您将不得不自己玩弄。虽然 Rust 编译器使这变得微不足道(只需遵循错误),但您可能希望让库为您自动处理它。否则,无论该操作是否可以使上下文无效,您最终可能只会为每个操作调用非常昂贵的操作。&mut PhEnsureValidContextwith_context

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

有人可能会争辩说我需要一个UnsafeCell或一个RefCell或一些其他的Cell。但是,从阅读本文来看,由于内部可变性,它似乎UnsafeCell只是一个 lang 项目——只有当你通过一个 改变状态时才需要它,而在这种情况下,我一直都有。&T&mut T

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

(和的完整代码PhEnsureValidContext包括 FFI 位,可在此处获得。)

4

2 回答 2

2

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

  • &T是一个可能被别名的引用T
  • &mut TT是保证不会被别名的引用。

因此,问题的关键是:保证不被别名是什么意思?


让我们考虑一个安全的 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:一个 inbar和一个 in foo,并且堆栈或其他寄存器中可能还有更多副本。

所以,很明显,存在别名。怎么来的!保证不会被别名!


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

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

因此,我认为,在任何时间点,如果 a&mut T是可访问的,那么对这个实例的其他引用一定是不可访问的。

拥有一个原始指针 ( *mut T) 非常好,它需要unsafe访问;但是,即使不使用它,形成第二个参考也可能是安全的,也可能不安全,所以我会避免它。

于 2018-03-27T12:51:44.667 回答
1

Rust 的内存模型还没有严格定义,所以很难确定,但我相信这不是未定义的行为

  1. 携带 a*mut Ph而 a&'a mut Ph也可以从另一条路径到达,只要您不取消引用*mut Ph即使只是为了阅读,也不要将其转换为 a &Phor &mut Ph,因为可变引用授予对指针的独占访问权限。
  2. 一旦另一个超出范围,就将其转换*mut Ph为 a 。&'a mut Ph&'a mut Ph
于 2018-03-27T03:23:50.440 回答