o11c
o11c

Reputation: 16076

How to `impl Clone for` a type that must not be allowed to move?

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

Answers (2)

Francis Gagn&#233;
Francis Gagn&#233;

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

Manishearth
Manishearth

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

Related Questions