如何强制联合表现得好像只有一种类型?

时间:2017-12-23 03:54:28

标签: rust

我正在尝试为epoll Linux API编写一个包装器。我分叉this存储库,但此包不使用union API使用的epoll类型。我决定使用Rust的C union功能来创建一个完整的包装器,用户不需要使用不安全的代码。

这个联盟给我带来了一些麻烦。

如何在编译时将联合使用的类型锁定为一种类型? epoll的联盟无法区分;你只能通过epoll fd使用union的一个成员。好吧,你可以,但这不安全。

用户可以使用枚举类型作为联合的ptr字段来使用多种类型,但这样做是安全的,因为它将使用Rust的enum

我用“通用”或“宏”搜索但我找不到任何符合我意愿的方法。

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) };
}

我想要那样的东西:

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

我的叉子可以找到here。我不知道宏,通用或其他什么是合适的解决方案。您可以查看我的代码,特别是the integration test,有很多不安全的代码,我想尽可能地从用户端删除不安全的代码。目前它看起来很丑陋。

1 个答案:

答案 0 :(得分:3)

您可以将联合包装在另一种类型中。该类型将具有特定特征的通用参数。然后,您可以根据具体实现实现特定的方法集。

在这种情况下,我们将联合Data包装在Event类型中。 Event具有实现EventMode的任何类型的泛型类型参数。我们为具体类型Event<Fd>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();
}

尝试拨打event.fd()会出错:

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

作为奖励,我们可以使用相同的PhantomData来编码我们在联合中隐藏的指针的具体类型,从而避免在我们检索它时将其不匹配。

严格来说,这个特性不是需要,但我认为它提供了一些不错的内置文档。

另见: