联合体的模式匹配

联合体的模式匹配比较受限, 每次只能匹配其中的一个成员, 先看一个示例:

#![allow(non_camel_case_types)]
#![allow(clippy::module_name_repetitions)]
#![allow(dead_code)]

use std::ffi::c_void;

#[repr(C)]
#[derive(Clone, Copy)]
pub union epoll_data_t {
    pub ptr: *const c_void,
    pub fd: i32,
    pub v_u32: u32,
    pub v_u64: u64,
}

fn main() {
    let data = epoll_data_t { fd: 2 };
    unsafe {
        match data {
            epoll_data_t { fd: 2 } => println!("stdout fd"),
            epoll_data_t { v_u32: 42 } => println!("u32 is 42"),
            epoll_data_t { v_u64: 28 } => println!("u64 is 28"),
            epoll_data_t { ptr } => if ptr.is_null() {
                eprintln!("NULL ptr")
            } else {
                println!("ptr: {ptr:?}")
            },
        }
    }
}

有时, 需要配合支持 C 语言中常用的 tagged-union 写法, 比如下面的例子是对 X11 的 XEvent 的封装:

use std::ffi::c_void;
use std::ptr;

#[repr(u8)]
#[derive(Debug, Default, Clone, Copy)]
pub enum EventType {
    #[default]
    Any,
    Keyboard,
    Button,
    Map,
}

pub type WindowId = i32;

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct XAnyEvent {
    pub event_type: EventType,
    pub serial: usize,
    pub send_event: bool,
    pub display: *const c_void,
    pub window: WindowId,
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct XKeyEvent {
    pub event_type: EventType,
    pub serial: usize,
    pub send_event: bool,
    pub display: *const c_void,
    pub window: WindowId,
    pub root: WindowId,
    pub x: i32,
    pub y: i32,
    pub x_root: i32,
    pub y_root: i32,
    pub keycode: u32,
}


impl XKeyEvent {
    #[must_use]
    #[inline]
    pub fn new(window: WindowId, x: i32, y: i32, keycode: u32) -> Self {
        Self {
            event_type: EventType::Keyboard,
            serial: 0,
            send_event: false,
            display: ptr::null(),
            window,
            root: window,
            x,
            y,
            x_root: x,
            y_root: y,
            keycode,
        }
    }
}

#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct XButtonEvent {
    pub event_type: EventType,
    pub serial: usize,
    pub send_event: bool,
    pub display: *const c_void,
    pub window: WindowId,
    pub root: WindowId,
    pub sub_window: WindowId,
    pub x: i32,
    pub y: i32,
    pub state: u32,
    pub button: u32,
}

#[repr(C)]
pub union XEvent {
    pub event_type: EventType,
    pub xany: XAnyEvent,
    pub xkey: XKeyEvent,
    pub xbutton: XButtonEvent,
}

fn main() {
    assert_eq!(size_of::<XEvent>(), 64);

    let event = XEvent {
        xkey: XKeyEvent::new(0x128a8, 40, 80, 95)
    };

    unsafe {
        match event.event_type {
            EventType::Any => println!("generic event"),
            EventType::Keyboard => {
                let x = event.xkey.x;
                let y = event.xkey.y;
                let keycode = event.xkey.keycode;
                println!("keyboard event at ({x}, {y}), keycode: 0x{keycode:0X}");
            }
            _ => eprintln!("Unhandled events"),
        }
    }
}

或者, 使用另一种风格的 tagged-union 写法, 这种的要更清晰一些. 下面这个例子支持单精度和双精度的方式来存储一个坐标点:

#![allow(dead_code)]

#[repr(u32)]
pub enum PointPrecision {
    F32,
    F64,
}

#[repr(C)]
union PointValue {
    v_f32: f32,
    v_f64: f64,
}

#[repr(C)]
struct Point {
    tag: PointPrecision,
    v: PointValue,
}

impl Point {
    pub const fn new_f32(value: f32) -> Self {
        Self {
            tag: PointPrecision::F32,
            v: PointValue { v_f32: value },
        }
    }

    pub const fn new_f64(value: f64) -> Self {
        Self {
            tag: PointPrecision::F64,
            v: PointValue { v_f64: value },
        }
    }

    #[allow(clippy::match_like_matches_macro)]
    pub fn is_zero(&self) -> bool {
        unsafe {
            match self {
                Self { tag: PointPrecision::F32, v: PointValue { v_f32: 0.0 } } => true,
                Self { tag: PointPrecision::F64, v: PointValue { v_f64: 0.0 } } => true,
                _ => false,
            }
        }
    }
}

fn main() {
    assert_eq!(size_of::<Point>(), 16);

    let point = Point::new_f32(3.12);
    assert!(!point.is_zero());
}