Cross-platform abstract design

⚓ Rust    📅 2025-08-02    👤 surdeus    👁️ 11      

surdeus

Warning

This post was published 123 days ago. The information described in this article may have changed.

I aim to implement platform abstraction where callers use a unified interface without platform-specific logic. My initial approach returned platform-specific types directly via conditional compilation:

// -- snap --
pub trait BtDevice {
    fn disconnect(&self) -> Result<(), Box<dyn std::error::Error>>;
}
​
#[derive(Default)]
pub struct WindowsBtDevice;
​
#[derive(Default)]
pub struct LinuxBtDevice;
​
#[derive(Default)]
pub struct MacOSBtDevice;
​
#[derive(Default)]
pub struct UnknownBtDevice;
​
pub struct Device;
​
impl Device {
    #[cfg(target_os = "windows")]
    pub fn new() -> WindowsBtDevice {
        WindowsBtDevice::default()
    }
    
    #[cfg(target_os = "linux")]
    pub fn new() -> LinuxBtDevice {
        LinuxBtDevice::default()
    }
       
    #[cfg(target_os = "macos")]
    pub fn new() -> MacOSBtDevice {
        MacOSBtDevice::default()
    }
 
    #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
    pub fn new() -> UnkonwnBtDevice {
        UnknownBtDevice::default()
    }
}
​
impl BtDevice for WindowsBtDevice {
    fn disconnect(&self) -> Result<(), Box<dyn std::error::Error>> {
        // do_something...
    }
}
​

Copilot Reviewer recommended refactoring to return a unified Deviceenum encapsulating all platforms:

// -- snap --
pub trait BtDevice {
    fn disconnect(&self) -> Result<(), Box<dyn std::error::Error>>;
}
​
#[derive(Default)]
pub struct WindowsBtDevice;
​
#[derive(Default)]
pub struct LinuxBtDevice;
​
#[derive(Default)]
pub struct MacOSBtDevice;
​
#[derive(Default)]
pub struct UnknownBtDevice;
​
pub enum Device {
    Windows(WindowsBtDevice),
    Linux(LinuxBtDevice),
    MacOS(MacOSBtDevice),
    Unknown(UnknownBtDevice),
}
​
impl Device {
    pub fn new() -> Self {
        #[cfg(target_os = "windows")]
        {
            Device::Windows(WindowsBtDevice::default())
        }
        #[cfg(target_os = "linux")]
        {
            Device::Linux(LinuxBtDevice::default())
        }
        #[cfg(target_os = "macos")]
        {
            Device::MacOS(MacOSBtDevice::default())
        }
        #[cfg(not(any(target_os = "windows", target_os = "linux", target_os = "macos")))]
        {
            Device::Unknown(UnknownBtDevice::default())
        }
    }
}
​
impl BtDevice for Device {
    fn disconnect(&self) -> Result<(), Box<dyn std::error::Error>> {
        match self {
            // i don't like this :< 
            Device::Windows(device) => device.disconnect(),
            Device::Linux(device) => todo!(),
            Device::MacOS(device) => todo!(),
            Device::Unknown(device) => todo!(),
        }
    }
    
}
​
impl BtDevice for WindowsBtDevice {
    fn disconnect(&self) -> Result<(), Box<dyn std::error::Error>> {
        // do_something...
    }
}
​

My concern: The match in BtDevice for Device requires boilerplate delegation for every platform variant. Is this enum-based design truly beneficial compared to the initial approach?

2 posts - 2 participants

Read full topic

🏷️ Rust_feed