Reputation: 16076
I'm wrapping objects from a C library that generates events on the object. In order to map the low-level event to the high-level one, I need to pass the address of the high-level object.
This all works.
mod c
{
enum Pen; // opaque struct
}
struct Pen
{
pen: *mut c::Pen,
event_hook: Option<Box<FnMut<(&mut Pen, EventDetails), ()>>>,
}
impl Pen
{
fn new() -> Box<Pen>
{
let rv = box Pen{pen: ..., event_hook: None};
... set up hook to use rv's raw address in a callback and forward to event_hook ...;
rv
}
}
impl Drop for Pen
{
fn drop(&mut self)
{
...;
}
}
The problem occurs when I try to impl Clone
. I can't impl Clone for Box<Pen>
because that conflicts with the implementation in liballoc. I can't impl Clone for Pen
because the address would change after I set up the low-level hooks.
The ability to clone this type is rather important. I could impl Pen
to give functions named .clone()
and .clone_from()
that actually return boxes, but that would prevent anyone from passing this object to a function that requires <T: Clone>
Upvotes: 2
Views: 700
Reputation: 65782
You need to separate the creation of your Pen
struct from the hooking and de-hooking.
use std::kinds::marker::NoCopy;
mod c
{
pub struct Pen; // opaque struct
}
struct Pen
{
pen: *mut c::Pen,
//event_hook: Option<Box<FnMut<(&mut Pen, EventDetails), ()>>>, // FIXME: this type does not compile
_no_copy: NoCopy
}
struct PenHookInner {
event_hook: Option<()> // TODO: maybe event_hook should move here from Pen?
}
struct PenHook<'a> {
pen: &'a Pen,
inner: PenHookInner,
}
impl Pen
{
fn new() -> Pen
{
let rv = Pen { pen: 0 as *mut c::Pen /*, event_hook: None*/, _no_copy: NoCopy };
rv
}
fn hook(&self) -> PenHook {
// TODO: set up hook to use self.pen's raw address in a callback and forward to event_hook
PenHook {
pen: self,
inner: PenHookInner { event_hook: None }
}
}
}
impl Drop for PenHookInner {
fn drop(&mut self) {
// TODO: remove the hook
}
}
fn main() {
let pen = Pen::new();
let pen_hook = pen.hook();
//drop(pen); //~ error: cannot move out of `pen` because it is borrowed
}
Pen
contains a NoCopy
field to make the struct not implicitly copyable. If you need to implement Drop
to free the "native" pen, then you don't need the NoCopy
field. (Types that implement Drop
are implicitly not Copy
.)
When a PenHook
is created, it borrows a reference to a Pen
. This prevents the Pen
from being moved or dropped while the PenHook
exists. As long as the PenHook
object exists, Pen
can't be moved or dropped.
If Pen
didn't have the NoCopy
field, it could still be implicitly copied: remove it and you'll see that the drop(pen);
line compiles. That's because drop()
receives a copy of pen
and drops that. Because Pen
doesn't implement Drop
in this sample, that's a no-op.
We now implement Drop
for PenHookInner
. PenHookInner
would contain the data that is necessary to remove the hook. We can't implement Drop
on PenHook
directly without #[unsafe_destructor]
, because of issue 11406. If we don't need to access the pen
borrowed pointer in the implementation of drop()
, then we can use a separate struct to work around it cleanly.
I made Pen::new()
return a Pen
rather than a Box<Pen>
, because there's no reason to box the result. If the caller wants to box the value, they can use box
at the call site.
...I was about to end with an open question about which type would implement Clone
, but then I read your comment about Pen
going away in the upstream library's next release. I suppose you wanted to clone both the pen and the hook. Obviously, with this structure, you wouldn't be able to do that easily. You'd be able to implement Clone
on Pen
easily, but for PenHook
, you'd need something else.
Upvotes: 1
Reputation: 16198
You can give Pen
a NoCopy
field that will prevent it from ever being implicitly copied.
Not sure if that's what you're asking for.
Upvotes: 0