作为将 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 Ph
EnsureValidContext
with_context
请注意,此代码是粗略的伪代码。我还没有编译或测试它。
有人可能会争辩说我需要一个UnsafeCell
或一个RefCell
或一些其他的Cell
。但是,从阅读本文来看,由于内部可变性,它似乎UnsafeCell
只是一个 lang 项目——只有当你通过一个 改变状态时才需要它,而在这种情况下,我一直都有。&T
&mut T
但是,我的阅读可能有缺陷。此代码是否调用 UB?
(和的完整代码Ph
,EnsureValidContext
包括 FFI 位,可在此处获得。)