Reputation: 2637
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?
Upvotes: 54
Views: 23361
Reputation: 27905
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 Unsigned
s 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 u16
s, 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.
Upvotes: 29
Reputation: 19416
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 impl
s to encapsulate the logic specific to u16
, u32
and u64
.
Upvotes: 41