5

In a library one may want to restrict implementations of a trait to be thread safe based on a feature flag. This sometimes involves changing trait inheritance. However, attributes are not allowed on the trait inheritance bounds. A common workaround is to copy the trait:

#[cfg(not(feature = "thread_safe"))]
pub trait MyTrait {
    fn foo();
}

#[cfg(feature = "thread_safe")]
pub trait MyTrait: Send + Sync {
    fn foo();
}

The duplicated code can be mitigated by using a macro (see below), but this can make the IDE experience suffer. Is there a better way to achieve conditional trait inheritance?

macro_rules! my_trait {
    ($($bounds:ident),*) => {
        pub trait MyTrait where $(Self: $bounds),* {
            fn foo();
        }
    };
}

#[cfg(not(feature = "thread_safe"))]
my_trait!();
#[cfg(feature = "thread_safe")]
my_trait!(Send, Sync);
  • Seems like a footgun to do this at all. Based on the information provided, I'd create [a trait alias](https://stackoverflow.com/q/26070559/155423) for `MyTrait + Send + Sync` instead. – Shepmaster Dec 16 '19 at 19:13
  • @Shepmaster I would use a trait alias (I did look into them before making this question), but they are still [unstable](https://github.com/rust-lang/rust/issues/41517). – AzureMarker Dec 16 '19 at 19:23
  • Yes, which is why I linked to a question that has [an answer](https://stackoverflow.com/a/26071172/155423) discussing how to approximate them with today's Rust as well as [an answer](https://stackoverflow.com/a/49263923/155423) mentioning the future with actual language-supported trait aliases (and that they are still unstable). – Shepmaster Dec 16 '19 at 19:25
  • Thanks, I would accept an answer that points out the footgun and links to trait aliases (including the stable workaround). – AzureMarker Dec 16 '19 at 19:28

1 Answers1

2

Based on looking at the maybe-sync crate (which you may want to use depending on your situation), I've found it's possible to do this by doing the following:

#[cfg(feature = "thread_safe")]
mod sync {
  pub use core::marker::Sync as MaybeSync;
  pub use core::marker::Send as MaybeSend;
}

#[cfg(not(feature = "thread_safe"))]
mod sync {
  pub trait MaybeSync {}
  impl<T> MaybeSync for T where T: ?Sized {}
  pub trait MaybeSend {}
  impl<T> MaybeSend for T where T: ?Sized {}
}

// then to use
use sync::MaybeSend;
use sync::MaybeSync;

pub trait MyTrait: MaybeSend + MaybeSync {
  fn foo();
}
David Sherret
  • 101,669
  • 28
  • 188
  • 178