将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 Ph
和EnsureValidContext
。尽管Rust编译器做到这一点并不重要(只需遵循错误),但您可能希望让该库为您自动处理它。否则,无论该操作是否会使上下文无效,您最终都可能只为每个操作调用非常昂贵的with_context
。请注意,此代码是粗略的伪代码。我还没有编译或测试它。
可能有人争辩说我需要
UnsafeCell
或RefCell
或其他一些Cell
。但是,由于内部可变性,从读取this来看,UnsafeCell
似乎只是一个lang项目-仅在通过&T
进行状态突变时才有必要,而在这种情况下,我会一直使用&mut T
。但是,我的阅读可能有缺陷。此代码是否调用UB?
(
Ph
和EnsureValidContext
的完整代码,包括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/