Why Rust compiler cannot implement this trait for me? Impl block for trait

Hello, everyone! Could you please explain why I get an error here:

trait StreamingClientCapability {}
trait Ping: StreamingClientCapability {}
trait Trades: StreamingClientCapability {}

trait StreamingClient {
    type Cap: StreamingClientCapability + ?Sized;
}

trait Client {}

trait Streaming<C: StreamingClient> {
    type Cap: StreamingClientCapability + ?Sized = C::Cap;

    fn new_stream_client(&self) -> C;
}

trait StreamingCapability<Cap: StreamingClientCapability + ?Sized> {}
impl<C: StreamingClient> StreamingCapability<C::Cap> for dyn Streaming<C, Cap = C::Cap> {}

struct BinanceStreamingClient {}
trait BinanceStreamingClientCap: Ping + Trades {}
impl StreamingClient for BinanceStreamingClient {
    type Cap = dyn BinanceStreamingClientCap;
}

struct BinanceClient {}
impl Client for BinanceClient {}

impl Streaming<BinanceStreamingClient> for BinanceClient {
    fn new_stream_client(&self) -> BinanceStreamingClient {
        todo!()
    }
}

trait RequiredClient: Client + StreamingCapability<dyn Ping> {}
impl<T: Client + StreamingCapability<dyn Ping>> RequiredClient for T {}

fn main() {
    let binance_client = BinanceClient {};
    let s: &dyn StreamingCapability<dyn Ping> = &binance_client.new_stream_client();
    let client: &dyn RequiredClient = &binance_client;
}

The idea is to show and enforce client capabilities using Rust's type. I expected StreamingCapability to be implemented for BinanceClient because it implements Streaming.
The compiler response is a bit vague..

For this line let client: &dyn RequiredClient = &binance_client; it says:

error[E0277]: the trait bound `BinanceClient: StreamingCapability<(dyn Ping + 'static)>` is not satisfied
  --> playground/type_theory/src/main.rs:76:39
   |
76 |     let client: &dyn RequiredClient = &binance_client;
   |                                       ^^^^^^^^^^^^^^^ the trait `StreamingCapability<(dyn Ping + 'static)>` is not implemented for `BinanceClient`
   |
   = help: the trait `StreamingCapability<<C as StreamingClient>::Cap>` is implemented for `(dyn Streaming<C, Cap = <C as StreamingClient>::Cap> + 'static)`
note: required for `BinanceClient` to implement `RequiredClient`
  --> playground/type_theory/src/main.rs:71:49
   |
71 | impl<T: Client + StreamingCapability<dyn Ping>> RequiredClient for T {}
   |                  -----------------------------  ^^^^^^^^^^^^^^     ^
   |                  |
   |                  unsatisfied trait bound introduced here
   = note: required for the cast from `&BinanceClient` to `&dyn RequiredClient`

I my head BinanceClient already implements Streaming and thus should also implement StreamingCapability<...>.

impl<C: StreamingClient> StreamingCapability<C::Cap> for dyn Streaming<C, Cap = C::Cap> {}

Why it doesn't happen?

Also on line let s: &dyn StreamingCapability<dyn Ping> = &binance_client; I get a very similar error.

error[E0277]: the trait bound `BinanceClient: StreamingCapability<dyn Ping>` is not satisfied
  --> playground/type_theory/src/main.rs:75:49
   |
75 |     let s: &dyn StreamingCapability<dyn Ping> = &binance_client;
   |                                                 ^^^^^^^^^^^^^^^ the trait `StreamingCapability<dyn Ping>` is not implemented for `BinanceClient`
   |
   = help: the trait `StreamingCapability<<C as StreamingClient>::Cap>` is implemented for `(dyn Streaming<C, Cap = <C as StreamingClient>::Cap> + 'static)`
   = note: required for the cast from `&BinanceClient` to `&dyn StreamingCapability<dyn Ping>`

Why rust cannot generate an implementation for StreamingCapability automatically? How can I help to do so (and keep the general approach)?

In general, dyn Trait is a type all its own. Rust will never produce a trait implementation for Foo<dyn Trait> just because you have one for Foo<SomeConcreteType>, or vice versa. In particular, your only impl of StreamingCapability is

impl<C: StreamingClient> StreamingCapability<C::Cap> for dyn Streaming<C, Cap = C::Cap> {}

and this means that the only types that implement StreamingCapability are dyn Streaming<...> types, but neither BinanceClient nor BinanceStreamingClient are one of those types, since they are struct types and not dyn types.

In general, if you want some trait to be implemented for all implementors of another trait, you don't do that with dyn:

impl Bar for dyn Foo {}

but instead with generics:

impl<F: ?Sized + Foo> Bar for F {}

Unrelatedly, as a piece of general advice: you are almost certainly way overusing dyn in general. I haven't figured out exactly what your code is trying to do, but I'd bet that to get it to actually work, you're going to need to remove lots more of the dyn you have. Good uses of dyn often have it only appear in a few usage sites (i.e. fields/parameters of type Box<dyn Trait> or &dyn Trait) and not at all in the traits or impls.

6 Likes
trait StreamingClientCapability {}
trait Ping: StreamingClientCapability {}
trait Trades: StreamingClientCapability {}

// …

trait BinanceStreamingClientCap: Ping + Trades {}

This looks like an attempt to translate some OOP inheritance hierarchy into Rust. That’s not something that’s typically particularly workable; as far as I’m aware, true hierarchies are only ever modeled (tediously / or rather with lots of generated code) for wrapping some FFI bindings that came with inheritence in the original language – for Rust, it’s generally advisable to do things the Rust way… more data oriented, more procedural, definitely less deep hierarchies.

As for what approach would best fit Rust in particular, it seems a bit hard on an incomplete (or toy?) example like this, where Ping and Trades are nothing but empty traits. Or maybe people with more OOP experience than me have a better chance at decyphering what the thing translated into Rust looked like originally and then can give better advice on how one could do it in Rust.

5 Likes

I guess that’s a good starting point to discuss how the code may be re-structured. Another important factor would be whether these “capabilities” are just like boolean flags, to model some set of boolean properties – or list of simply marker flags – for stuff under the hood that might error or misbehave if used with the wrong or missing “capabilities”; or whether these “capabilities” should come with API (i.e. methods) attached, and if so, which part / which type has those methods?


Another observation I’d like to share after looking through your code a few more times: Rust doesn’t support generalizing over traits. If Ping or Trades are traits, then you cannot put them into generic argument position; so you’ve resorted to using dyn Ping then, but Rust doesn’t offer any ways to relate dyn Ping to the types that implement it, and even less to to the dyn Foo types of subtraits Foo. So the whole setup can’t work like this at all.

1 Like

Thank you guys! Your responses are really helpful!
As I understood two main reasons why it doesn't work is that Rust doesn't link dyn Ping with BinanceStreamingClientCap and dyn Streaming<...> with any actual type implementing it (at least in my code). Could you please guide me where can I learn more about this behavior and maybe about advanced topics like this in general?

I looking to build an abstraction that would allow to check client capabilities at compile time. I was actually able to implement it but it requires a lot of boilerplate code to get up and running... Especially a lot of generic parameters the closer you get to Client trait. Code above is my attempt to refactor that and hide redundant details.

In general the desired outcome looks like this:

  • I have dedicated clients for multiple markets
  • Each of those clients can have specific and generic functionality.
  • Want to generalize over that and define client capabilities using traits, some clients have websocket connection others don't. I also want to show what data/events is available using websocket. Same for REST/RPC api's.
  • I want to write a trading strategy/data collection/backtest in generic fashion by specifying what capabilities I need from a client. Example:
    • Data collection can work with any market client that supports streaming orderbook and trades (this requires a streaming client capability as you can see above). So I define a type that covers all clients with streaming orderbook.
    • I can have a trading strategy that covers all clients that can stream orderbook AND perform spot trades.
    • For anything that does not fall under generic requirements I can implement a specialized strategy etc.
trait TradingStrategy {
    fn trade(&self);
}

// Warning, just a pseudocode, there are goes a lot of generic parameters and it's get overwhelming fast
trait GenericClientStreamingCap: TradesStream + BookStream {}
trait GenericClient<...>
where
    // Instruments are also linked to specific markets SpotTrading<Binance> and SpotTrading<Bybit>
    // can provide a lot of different methods, by using SpotTrading<T> we know that at least some basic
    // trading functions like place_order are there and we don't care what else they can do.
    T: MarketType, 
    Self: Streaming<T, GenericClientStreamingCap>,
    Self: SpotTrading<T> {}
struct GenericStrategy<...> {
  client: &'a dyn GenericClient<...>;
}
impl TradingStrategy for GenericStrategy<...> {
    fn trade(&self) {
        if self.wallet().check_balance() {
            self.spot().place_order(...);
        }
    }
}

struct BinanceStrategy<'a> {
    client: &'a BinanceClient;
}
impl TradingStrategy for Binance {
    fn trade(&self) {
        // You have dedicated wallets for different instruments here.
        if self.spot_wallet().check_balance() {
            self.spot().place_order(...);
        } else {
            self.spot_wallet().add_funds(...); // Hope you get the idea
        }
    }
}

let binance_client = BinanceClient::new();
let kraken_client = KrakenClient::new();
let bybit_client = BybitClient::new();
let strategies: Vec<&dyn TradingStrategy> = vec![
    BinanceStrategy { client: &binance_client },
    GenericStrategy { client: &kraken_client },
    GenericStrategy { client: &bybit_client },
];

Sooo again, could point me to resources where I can learn more about these more advanced topics in Rust please? I feel like I don't fully understand what's possible in Rust and whats not.

I don't know that there's a good resource here; here's the official material that's available:

The problem is that the thing you're looking for isn't so much a behavior as a lack of behavior. dyn Foo is a type and it does not have any special properties in the type system about being equal to other non-dyn types. The special features dyn Foo has are that:

  • it (usually) implements the Foo trait as if impl Foo for dyn Foo had been written.
  • it can be coerced from certain other types — but this only happens when it is forced to happen by a type mismatch at potential coercion sites.

Don't think of this as “why is this not happening”, think of this as “nothing says it does happen”.

2 Likes

On trait objects (dyn) this resource from @quinedot has a lot of useful info, on other topics as well.

4 Likes

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.