3

I'm developing an application where I have a struct and a trait: Foo and Bar.

Foo owns a bunch of Bars and different Foos have different Bars. I need to filter Foos based on which Bar they have. What I'm trying to do is give each implementation of Bar a bit flag and then use all the flags to identify which Bars a Foo owns.

While I'm not sure this is the best approach to do this kind filtering, it works. The problem is I have to manage every single flag for every type, I can't repeat the flags or skip them so I wanted a way to do this automatically at compile time (or runtime, as long as it follows the same constraints).

This is my latest attempt at solving this:

#[macro_use]
extern crate lazy_static;

static mut V : u32 = 0u32;
trait Mask<T> {
    fn mask() -> u32 {
        unsafe {
            V = V + 1;
            println!("v {}", V);
            lazy_static!(static ref VALUE : u32 = unsafe{V};);
            *VALUE
        }
    }
}

#[derive(Default)]
struct A;
#[derive(Default)]
struct B;

impl Mask<A> for A {}
impl Mask<B> for B {}

fn main() {
    println!("{}", A::mask());
    println!("{}", B::mask());
    println!("{}", A::mask());
    println!("{}", A::mask());
    println!("{}", B::mask());
}

Result:

v 1
1
v 2
1
v 3
1
v 4
1
v 5
1

This is an idea I got from C++ templates which, if I recall correctly, would generate a different type for each Mask<T> implementation and the mask() call would be unique for the same type, but different between other types. It doesn't work in Rust but I don't understand enough about Rust's generic types to guess why. I also tried using macros, but I don't know if there's a way to save state between macro calls.

Is there a way to achieve this? Is there something else I can try for this kind of filtering?

This is not a duplicate of Generating unique ID's for types at compile time. Unless there is a way to create bit flags for each type using numbers like 17767285367811814831, which is what TypeId gives me.

What I want in the end is for each type to be something like this:

struct A;
struct B;
//[...]
struct N;

impl Mask for A {
    fn mask() -> u32 {
        1 << 0
    }
}
impl Mask for B {
    fn mask() -> u32 {
        1 << 1
    }
}
//[...]
impl Mask for N {
    fn mask() -> u32 {
        1 << ?N?
    }
}
Community
  • 1
  • 1
Luke B.
  • 1,258
  • 1
  • 17
  • 28
  • Your example code increments by one, so it is *not* a bitmask. You also state that you want to be able to filter by type, which you can do with an arbitrary number / `TypeId`. Can you expand more why it needs to be a bitmask? – Shepmaster Dec 08 '15 at 15:48
  • 1
    @Shepmaster Unless I'm using the wrong word (which is a big possibility), afaik `1 << 0` gives me `0b001`, `1 << 1` gives me `0b010`, `1 << 2` gives me `0b100`. Is this not how bitmasks are constructed? That's how I have always done. About needing to be bitmasks, it doesn't, that why I said I would accept better solutions, but If I were to filter using TypeId it would be much more complicated than using masks. – Luke B. Dec 08 '15 at 15:57
  • You are correct that shifting would give you a bitmask, but your "latest attempt at solving this" simply adds one each time it was called, so I figured that's what you actually wanted. I'm not sure why a bitmask would be easier to filter on than a straight equality test, but if you are certain it's not a duplicate, I can reopen. – Shepmaster Dec 08 '15 at 16:04
  • My latest attempt was an example of code generation, I'll try to be clearer next time. I feel it's easier because bit operations are easier (and faster?) than checking every typeId (besides, I keep them in a Vec> so I don't think I can really use TypeId here, since I lost the original type). – Luke B. Dec 08 '15 at 16:10

1 Answers1

4

If you can list all the types together, you can do this with a macro and using an increasing sequence of enum discriminants like this:

trait Mask {
    fn mask() -> u32;
}

macro_rules! make_masks {
    ($($t:ident),+) => {
        enum Masks {
            $($t),+
        }

        $(
            impl Mask for $t {
                fn mask() -> u32 {
                    1 << (Masks::$t as u32)
                }
            }
        )+
    };
}

struct A;
struct B;
struct C;

make_masks!(A, B, C);

fn main() {
    // Output is: 1, 2, 4
    println!("{:x}, {:x}, {:x}", A::mask(), B::mask(), C::mask());
}

(Playground link)

bluss
  • 12,472
  • 1
  • 49
  • 48
  • A nice thing about this solution is that with a bit of tweaking, you can extend it to provide different masks for different sets of objects. Since you are limited to 32 (or 64 is you pick `u64`) types, this may come in handy. Or maybe you have different categories of types. – Shepmaster Dec 08 '15 at 16:26
  • True, using usize is just wrong. Lucky that's not the most important part.. I'll update it anyway. – bluss Dec 08 '15 at 16:28