0

I have a queue Strategy trait with implementations for Monotonic and LastTick parameterised on the type I want to insert:

struct Monotonic<T> {
    items: Vec<T>,
}
struct LastTick<T> {
    items: Vec<T>,
}

struct SetDelta;

trait Strategy<T> {
    type T;
    fn enqueue(&mut self, v: T);
    fn has_pending(&self) -> bool;
}

impl<T> Strategy<T> for Monotonic<T> {
    type T = Self;
    fn enqueue(&mut self, v: T) {
        self.items.push(v);
    }
    fn has_pending(&self) -> bool {
        !self.items.is_empty()
    }
}

impl<T> Strategy<T> for LastTick<T> {
    type T = Self;
    fn enqueue(&mut self, v: T) {
        self.items.push(v);
    }
    fn has_pending(&self) -> bool {
        !self.items.is_empty()
    }
}

impl<T> Strategy<T> for LastTick<T> {
    type T = Self;
    fn enqueue(&mut self, v: T) {
        self.items.push(v);
    }
    fn has_pending(&self) -> bool {
        !self.items.is_empty()
    }
}

impl<T> LastTick<T> {
    fn new() -> Self {
        LastTick { items: Vec::new() }
    }
}
impl<T> Monotonic<T> {
    fn new() -> Self {
        Monotonic { items: Vec::new() }
    }
}

#[test]
fn monotonic_scalar_queue() {
    let mut a = Monotonic::<f64>::new();
    a.enqueue(123.4);
    assert!(a.has_pending());
}

#[test]
fn monotonic_list_queue() {
    let mut a = Monotonic::<[f64; 3]>::new();
    a.enqueue([123.4, 345.8, 324.1]);
    assert!(a.has_pending());
}

#[test]
fn monotonic_tuple_queue() {
    let mut a = Monotonic::<(f64, String, u32)>::new();
    a.enqueue((123.4, "hello".into(), 324));
    assert!(a.has_pending());
}

The above work fine. I want to keep the same interface for a HashSet where the behaviour is slightly different.

#[test]
fn monotonic_set_queue() {
    let mut a = Monotonic::<HashSet<f64>>::new();
    // I want to insert a f64 and implement the logic of the hashset in
    // the implementation, but it expects a new HashSet
    a.enqueue(123.4);
    assert!(a.has_pending());
}

I tried

impl<T> Strategy<T> for Monotonic<HashSet<f64>> {
    type T = Self;
    fn enqueue(&mut self, v: T) {
        self.items.push(v);
    }
    fn has_pending(&self) -> bool {
        !self.items.is_empty()
    }
}

and also

impl Strategy<f64> for Monotonic<f64> {
    type T = HashSet<f64>;
    fn enqueue(&mut self, v: f64) {
        self.items.push(v);
    }
    fn has_pending(&self) -> bool {
        !self.items.is_empty()
    }
}

with different results, but no luck. Is there a way to specify this easily?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Delta_Fore
  • 3,079
  • 4
  • 26
  • 46
  • See also [How can I use a HashMap with f64 as key in Rust?](https://stackoverflow.com/q/39638363/155423); [How to do a binary search on a Vec of floats?](https://stackoverflow.com/q/28247990/155423) – Shepmaster Mar 20 '19 at 20:08

1 Answers1

1

It seems like you want a different implementation of Monotonic<T>, where the collection is not a Vec - that's not possible with the way you have currently defined Monotonic. You could, instead, create another type MonotonicHashSet<T>, and use HashSet as the backing collection.

If, instead, you would like to make Monotonic accept different collection types, then you may want to also genericize it over the collection type. This, however, could get complicated quickly. In Rust, the properties we generally associate with collections are split across several traits, defined in the iter module. They're split up so that each collection type can define it's behavior granularly and correctly for whatever constraints the collection has. So, for your Monotonic and LastTick types, it's important to consider what requirements you might have, and what traits that means you will require for collections to be used with that type.

One last note: While Vec accepts any type T, a HashSet requires total equality from the Eq trait, and hashability, through the Hash trait. These different requirements are worth considering, because unlike, for example, C#, Rust does not provide default implementations of these operations for all types - you have to provide them or #[derive()] them.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Zarenor
  • 1,161
  • 1
  • 11
  • 23
  • One last thing - the floating-point types (`f32`& `f64`) don't implement `Eq` or `Hash` - `Eq`, because `NaN` is not equal to any number or itself; and `Hash` because there is no consensus on how to deal with +/-0, NaN, denormals, and there are issues with different bitwise representations (which may hash differently) representing the same number. – Zarenor Mar 20 '19 at 19:36