47

I'm trying to create a generic struct which uses an "integer type" for references into an array. For performance reasons I'd like to be able to specify easily whether to use u16, u32 or u64. Something like this (which obviously isn't valid Rust code):

struct Foo<T: u16 or u32 or u64> { ... }

Is there any way to express this?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Henning Koehler
  • 2,456
  • 1
  • 16
  • 20

2 Answers2

38

For references into an array usually you'd just use a usize rather than different integer types.

However, to do what you are after you can create a new trait, implement that trait for u16, u32 and u64 and then restrict T to your new trait.

pub trait MyNewTrait {}

impl MyNewTrait for u16 {}
impl MyNewTrait for u32 {}
impl MyNewTrait for u64 {}

struct Foo<T: MyNewTrait> { ... }

You may then also add methods onto MyNewTrait and the impls to encapsulate the logic specific to u16, u32 and u64.

Lukazoid
  • 19,016
  • 3
  • 62
  • 85
  • 1
    Yes, but that does not allow me to do everything I could do with an u16, u32 or u64 - e.g. I could not use a value to retrieve an element of an array, compare two values, etc. – Henning Koehler Nov 24 '16 at 02:58
  • 4
    @HenningKoehler: You can, you just have to declare it at trait level. That is `pub trait MyNewTrait: Add + Mul + ... {}` – Matthieu M. Nov 24 '16 at 07:48
  • I suppose, but that also becomes tedious pretty quickly.. and for referencing into a vector I'd need a conversion into usize, which doesn't seem very safe for arbitrary types that can be added, multiplied etc... – Henning Koehler Nov 24 '16 at 09:33
  • 3
    @HenningKoehler You could add another trait restriction on your struct or trait for `Into`. Either `T: MyNewTrait + Into` or `trait MyNewTrait : Into` – Lukazoid Nov 24 '16 at 10:46
24

Sometimes you may want to use an enum rather than a generic type with a trait bound. For example:

enum Unsigned {
    U16(u16),
    U32(u32),
    U64(u64),
}

struct Foo { x: Unsigned, ... };

One advantage of making a new type over implementing a new trait for existing types is that you can add foreign traits and inherent behavior to the new type. You can implement any traits you like for Unsigned, like Add, Mul, etc. When Foo contains an Unsigned, implementing traits on Unsigned doesn't affect the signature of Foo like it would to add them as bounds on Foo's parameter (e.g. Foo<T: Add<Output=Self> + PartialCmp + ...>). On the other hand, you do still have to implement each trait.

Another thing to note: while you can generally always make a new type and implement a trait for it, an enum is "closed": you can't add new types to Unsigned without touching the rest of its implementation, like you could if you used a trait. This may be a good thing or a bad thing depending on what your design calls for.


"Performance reasons" is a bit ambiguous, but if you're thinking of storing a lot of Unsigneds that will all be the same internal type, and this:

struct Foo([Unsigned; 1_000_000]);

would waste a ton of space over storing a million u16s, you can still make Foo generic! Just implement From<u16>, From<u32>, and From<u64> for Unsigned and write this instead:

struct Foo<T: Into<Unsigned>>([T; 1_000_000]);

Now you only have one simple trait bound on T, you're not wasting space for tags and padding, and functions that deal with T can always convert it to Unsigned to do calculations with. The cost of the conversion may even be optimized away entirely.

See Also

trent
  • 25,033
  • 7
  • 51
  • 90
  • 1
    omg. Exactly what I was looking for lol. I didn't realize that I could use tuple structs inside enums that way, but now that I think about it it really makes a lot of sense. – Lazerbeak12345 Aug 20 '21 at 14:38
  • 2
    Just to add that the `num` crate already did the job for you: https://docs.rs/num/latest/num/traits/trait.Unsigned.html – Anonyme2000 Mar 10 '22 at 20:00