3

I'm trying to write a wrapper for the epoll Linux API. I forked this repository, but this crate doesn't use the union type used by the epoll API. I decided to use Rust's C union feature to create a complete wrapper where user will not need to use unsafe code.

This union causes me some trouble.

How could I lock the used type of the union to one type at compile time? epoll's union can't be differentiated; you can only use one member of the union by epoll fd. Well, you can but that will not be safe.

The user could use an enum type as the ptr field of the union to use multiple types but this will be safe because it will use Rust's enum.

I searched with "generic" or "macro" but I can't find any way that will fit my will.

extern crate libc;

#[derive(Clone, Copy)]
pub union Data {
    pub ptr: *mut libc::c_void,
    pub fd: std::os::unix::io::RawFd,
    pub u32: libc::uint32_t,
    pub u64: libc::uint64_t,
}

#[repr(C)]
#[repr(packed)]
#[derive(Clone, Copy)]
pub struct Event {
    pub data: Data,
}

impl Event {
    fn new(data: Data) -> Event {
        Event { data: data }
    }
}

fn main() {
    let event = Event::new(Data {
        ptr: Box::into_raw(Box::new(42)) as *mut libc::c_void,
    });
    unsafe { Box::from_raw(event.data.ptr) };
}

I would like something like that:

fn main() {
    let event = event!(ptr, Box::new(42));
    let _ = event.ptr();
    let _ = event.fd(); // fails to compile; we can only use ptr
}

My fork can be found here. I don't know what macro, generic or other could be the suitable solution. You can look at my code, especially the integration test, there is a lot of unsafe code, I want to remove as much as possible of unsafe code from user side. Currently it looks very ugly.

Stargateur
  • 24,473
  • 8
  • 65
  • 91

1 Answers1

4

You can wrap the union in another type. That type will have a generic parameter of a specific trait. You can then implement specific sets of methods based on the concrete implementation.

In this case, we wrap the union Data in the type Event. Event has a generic type parameter for any type that implements EventMode. We implement specific methods for the concrete types Event<Fd> and Event<Ptr>:

extern crate libc;
use std::os::unix::io::RawFd;
use std::marker::PhantomData;

#[derive(Copy, Clone)]
pub union Data {
    pub ptr: *mut libc::c_void,
    pub fd: RawFd,
}

trait EventMode {}

#[derive(Debug, Copy, Clone)]
struct Fd {
    _marker: PhantomData<RawFd>,
}
impl Fd {
    fn new() -> Self {
        Fd {
            _marker: PhantomData,
        }
    }
}
impl EventMode for Fd {}

#[derive(Debug, Copy, Clone)]
struct Ptr<T> {
    _marker: PhantomData<Box<T>>,
}
impl<T> Ptr<T> {
    fn new() -> Self {
        Ptr {
            _marker: PhantomData,
        }
    }
}
impl<T> EventMode for Ptr<T> {}

#[derive(Copy, Clone)]
pub struct Event<M> {
    pub data: Data,
    mode: M,
}

impl Event<Fd> {
    fn new_fd(fd: RawFd) -> Self {
        Event {
            data: Data { fd },
            mode: Fd::new(),
        }
    }

    fn fd(&self) -> RawFd {
        unsafe { self.data.fd }
    }
}

impl<T> Event<Ptr<T>> {
    fn new_ptr(t: T) -> Self {
    let ptr = Box::into_raw(Box::new(t)) as *mut _;
        Event {
            data: Data {
                ptr: ptr,
            },
            mode: Ptr::new(),
        }
    }

    fn ptr(&self) -> &T {
        unsafe { &*(self.data.ptr as *const T) }
    }
}

fn main() {
    let event = Event::new_ptr(42);
    println!("{}", event.ptr());
    // event.fd();
}

Attempting to call event.fd() gives the error:

error[E0599]: no method named `fd` found for type `Event<Ptr<{integer}>>` in the current scope
  --> src/main.rs:76:11
   |
76 |     event.fd();
   |           ^^

As a bonus, we can use the same PhantomData to encode what the concrete type of the pointer we have stashed in the union is and thus avoid mismatching it when we go to retrieve it.

Strictly speaking, the trait isn't needed, but I think it provides some nice built-in documentation.

See also:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366