Binding struct generic type parameter by enum

I'm a Rust newbie with TypeScript background and I guess I'm trying to recreate something that would only work in TS, but I'm still curious what is the Rust way to achieve this.

pub enum OrderType {
  SELL,
  BUY,
}

pub enum Asset {
  Linen = "Linen",
  Leather = "Leather",
  Copper = "Copper",
}

pub struct Order<T: OrderType> {
  pub order_type: T,
  pub price: String,
  pub quantity: u64,
}

pub struct Engine<A: Asset> {
  pub asset: A,
  pub asks: Vec<Order<OrderType::SELL>>,
  pub bids: Vec<Order<OrderType::BUY>>,
}

impl<A: Asset> Egnine<A> {
  pub fn new(asset: A) -> Engine<A> {
    Engine {
      asset,
      asks: Vec::new(),
      bids: Vec::new(),
    }
  }

  pub fn place(&self, order: Order) {
    match order.order_type {
      OrderType::SELL => self.asks.push(order),
      OrderType::BUY => self.bids.push(order),
    }
  }
}

fn main() {
  let engine = Engine::new(Asset::Linen);
}

Generic type parameters T: OrderType and A: Asset are tagged with the error expected trait, found enum. not a trait, and my guess is that I'm missing something fundamental here, but unfortunately I'm unable to find a satisfactory solution to my problem. All I really want is to bind generic type parameter by enum somehow, so that I have structs of specific types

I don't understand what you mean by "bind generic type parameter by enum". An enum is a type, and a generic parameter is also a type. A generic type parameter can be bounded by a trait, which results in the compiler requiring that all concrete types substituted for it implement that trait. The type itself may still be anything (that is the point of generics); it doesn't make sense to specify a concrete type as a bound for a generic type parameter.

I know some TypeScript too, but I have no idea what TS pattern this is supposed to replicate. How do you want to use this? What effect do you want it to have?

Sorry, my terminology is still wonky. Here's what I'm trying to replicate: TS playground. If I understand you correctly, the only way to constrain a generic type parameter is with a trait, and generics themselves can be any type whatsoever, which is my fundamental misunderstanding of Rust. What I'm trying to do is to have two 'substructs' of Order and three 'substructs' of Engine, but without inheritance, if this makes sense

Your TS code doesn't need generics either. You can rewrite this:

interface Order<T extends OrderType> {
    type: T,
    price: string,
    quantity: number,
}

as

interface Order {
    type: OrderType,
    price: string,
    quantity: number,
}

And the rest of your code will still work.

You can do the same with Rust, and pattern match on the type field.

1 Like

In Rust, enum variants aren't types, and there is no flow-sensitive typing. You can replicate the example with marker types:

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum OrderType {
    Sell,
    Buy,
}

#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct Sell;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
struct Buy;

#[derive(Clone, Copy, PartialEq, Debug)]
struct Order<T> {
    order_type: T,
    price: f64,
    quantity: f64,
}

impl<T> Order<T> {
    fn with_type<U>(self, order_type: U) -> Order<U> {
        Order {
            order_type,
            price: self.price,
            quantity: self.quantity,
        }
    }
}

#[derive(Clone, Default, Debug)]
struct Engine {
    asks: Vec<Order<Sell>>,
    bids: Vec<Order<Buy>>,
}

impl Engine {
    fn place(&mut self, order: Order<OrderType>) {
        match order.order_type {
            OrderType::Sell => self.asks.push(order.with_type(Sell)),
            OrderType::Buy => self.bids.push(order.with_type(Buy)),
        }
    }
}

fn main() {
    let mut engine = Engine::default();

    engine.place(Order { order_type: OrderType::Sell, price: 10.0, quantity: 10.0 });
    engine.place(Order { order_type: OrderType::Buy,  price: 5.0, quantity: 10.0 });
    
    dbg!(engine);
}
1 Like

As for this code:

Bare in mind that you can't do this in Rust. In Rust you have more powerful enums that in TS, so you can do something like this:

enum OrderType {
    Sell(Sell),
    Buy(Buy),
}

struct Sell;

struct Buy;

Or you can do as @H2CO3 wrote above while I was typing this :sweat_smile: .

1 Like

Strictly speaking, yes, at this stage there's no point in differentiating between Engine's with different assets and Order's with transaction direction, but I just want to make my code future-proof in case I want to implement asset or order type specific behavior (which may never come up, of course) and somehow make it more explicit, but maybe I'm still wrong on this one, since explicitness may add nothing, I will still need to pattern-match against all cases possible

It's still a bit hard to comprehend, but I got the gist of your answer. Thanks for the pointers!

The point is: OrderType, Sell, and Buy are all different types. OrderType has actual information content, so it lets you choose dynamically, while Sell and Buy are marker types with no information content (they are unit types), but they can ensure that Sell and Buy orders are never contained in the same data structure.

What I think you wanted is, given an order of dynamic type (Order<OrderType>), group them into separate statically-typed containers (with element types Order<Sell> and Order<Buy>). My solution does that by matching on the type and then changing the marker type of the Order based on the runtime value of the dynamic OrderType, nothing more.

1 Like

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.