Trait necessary for cross-platform interface?

Hi all,

I'm the maintainer of https://github.com/hwchen/keyring-rs, which provides a linux/windows/macos interface to respective system vaults. I wrote this when I was pretty young at Rust, I simply made three different modules which exported identical public interfaces to structs and methods.

Now, there's finally been a request for a platform-specific (access to a specified Keychain on mac).

My question is whether it's worth it now to enforce the interface through a trait, and allow platform-specific features through an extension trait.

I guess the other options would be to just allow one-off functionality and document well, or see if I can find a more generic cross-platform method that would include the functionality of setting a keychain.

I'm leaning towards just allowing the one-off functionality because it's simple and doesn't introduce a breaking change. But I don't want to be setting myself up for heartbreak down the road. Any feedback is greatly appreciated.

One pattern I've seen is to give users access to the platform-specific implementation, but also define a convenience function that uses impl Trait to return an instance specific to that platform.

pub trait KeyRing { ... }

pub fn default_keyring() -> impl KeyRing {
  cfg_if! {
    if #[cfg(windows)] { 
      windows::WindowsKeyRing::default()
    } else if #[cfg(linux)] {
      linux::LinuxKeyRing::default()
    }
  }
}

#[cfg(windows)]
pub mod windows { 
  pub struct WindowsKeyRing { ... }
  impl KeyRing for WindowsKeyRing { ... }
}

#[cfg(linux)]
pub mod linux { 
  pub struct LinuxKeyRing { ... }
  impl KeyRing for LinuxKeyRing { ... }
}

This way when you only care about the functionality defined by KeyRing you can use the convenience function, but you also have access to the concrete type implementing KeyRing if you need an escape hatch.

10/10 would recommend.

It's so much easier to use a trait to make sure the different implementations have identical interfaces. The only alternative would be to have tests/examples which touch every single bit of functionality and make sure you have CI jobs for all supported architectures so any divergence is picked up. You probably already have tests for each architecture, but ensuring they cover 100% of your API is kinda a pain.

1 Like

Thanks for the advice. I think I’ll move in that direction.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.