作为将 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.)