在 Rust 中借助原始指针进行运行时借用管理是否是未定义的行为?

2024-04-06

作为将 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 */
}

这可以通过在必要时检查我们是否是Ph or a 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 like:

/* 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");

Without vph,你必须兼顾&mut Ph and EnsureValidContext自己到处走走。虽然 Rust 编译器使这变得微不足道(只需跟踪错误),但您可能希望让库自动为您处理它。否则你可能最终只会打电话给非常昂贵的with_context对于每个操作,无论该操作是否会使上下文无效。

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

有人可能会说我需要一个UnsafeCell or a RefCell或其他一些Cell。然而,从阅读this https://github.com/rust-lang/rust/issues/34496它出现UnsafeCell只是一个 lang 项,因为内部可变性— 仅当您通过以下方式改变状态时才需要&T,而在这种情况下我有&mut T一路走来。

然而,我的阅读可能有缺陷。这段代码会调用 UB 吗?

(完整代码Ph and EnsureValidContext,包括 FFI 位,可用here https://bitbucket.org/SoniEx2/hexchat-plugin.rs.)


退一步来说,Rust 所支持的保证是:

  • &T是对T这可能是别名,
  • &mut T是对T保证不会出现别名。

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


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

所以,很明显,有are存在别名。怎么会!保证不会出现别名!


深吸一口气:你能找到多少个这样的别名access当时?

Only 1. 无混叠的保证不是空间上的,而是时间上的。

因此,我认为,在任何时候,如果&mut T是可以访问的,那么没有其他参考此实例必须是可访问的。

有一个原始指针(*mut T)完全没问题,它需要unsafe访问;然而,即使不使用它,形成第二个参考也可能安全,也可能不安全,所以我会避免它。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Rust 中借助原始指针进行运行时借用管理是否是未定义的行为? 的相关文章

随机推荐