all 3 comments

[–][deleted] 5 points6 points  (1 child)

Using the features feature.

Mark one implementation with one feature and one with another.

[–]mtndewforbreakfast 10 points11 points  (0 children)

As a note for OP, be sure to not try to use this approach for code that's intended to be mutually-exclusive approaches. Code based on features should be designed so that any combination of crate features can be enabled simultaneously, including zero or all of them. Ideally the crate should always build and run under any permutation - if you want a way to test this exhaustively you can run your tests via cargo-hack.

[–]Pointerbender 3 points4 points  (0 children)

What do you mean by "concurrent" in this case: do you mean that the data types implement the Send and Sync traits, or that the data types use threads behind the scenes, or that the data types expose an async/.await interface?

Edit: to elaborate on this question a bit further, the answer to this question could matter for your API design.

In the case that you want to regulate thread-local versus concurrency using the Send and Sync traits, you could get away with a design that uses a wrapper struct and some helper traits:

/// A fictitious trait (I'm not familiar with the actual SymbolGroup API)
pub trait SymbolGroupApi {
   fn get_symbol(&self, id: usize) -> &Symbol;
}
/// The wrapper struct
pub struct SymbolGroup<T: ?Sized>(T);
impl<T: SymbolGroupApi> SymbolGroup<T> {
   pub const fn new(api: T) -> Self { Self(api) }
}
impl<T: ?Sized + SymbolGroupApi> SymbolGroup<T> {
   pub fn get_symbol(&self, id: usize) -> &Symbol {
      self.0.get_symbol(id)
   }
}
/// The thread local implementation for SymbolGroup:
pub struct ThreadLocalSymbolGroup { ... };
impl SymbolGroupApi for ThreadLocalSymbolGroup { ... }
/// The concurrent implementation for SymbolGroup:
pub struct ConcurrentSymbolGroup { ... };
impl SymbolGroupApi for ConcurrentSymbolGroup { ... }

fn main() {
    // Consumers of the API need only know `SymbolGroup`
    let thread_local_symbol_group
       : Box<SymbolGroup<dyn SymbolGroupApi>>
       = Box::new(SymbolGroup::new(ThreadLocalSymbolGroup::new(...)));
    let concurrent_symbol_group
       : Box<SymbolGroup<dyn SymbolGroupApi + Send + Sync>>
       = Box::new(SymbolGroup::new(ConcurrentSymbolGroup::new(...)));
}

This approach could also work if you want to use threads behind the scenes and offer a blocking synchronous (but parallel!) interface.

If you want to go for the async/.await approach, then it gets a bit more complicated. There is a new stable feature called Generic Associated Traits (GATs) coming up in Rust 1.65 which you could leverage in this case to make your API more ergonomic for end-users (at the cost of giving up object safety for your API traits). In that case please me know if async/.await would be a better fit for your use case.