How to Avoid Generic Structs?

I had read that it’s better not to bound structs, but I wonder how? I want :thread: Stringlet to be type safe. It has four slightly different variants with varying optimisations. Since I can’t have an enum as generic marker, I went for marker structs. Each variant has slightly different characteristics, which I implemented on a Config trait. (All done in lib.rs through quite a big macro.)

E.g. if the marker is Slim, SIZE is limited to 0..=64 (because that uses an optional 6-bit UTF-8 niche.) And LEN = 0, because this variant doesn’t need a length byte.

Whereas if the marker is Var, SIZE is limited to 0..=255, because it adds one byte for the length. I put that byte into an array of LEN = 1 to make it easy to create variant-agnostically. Alas const generics are still quite rudimentary, so I couldn’t find a way to calculate LEN or get it from Config. So that’s another generic parameter.

Now this is hidden behind type aliases Stringlet<SIZE>, VarStringlet<SIZE>, TrimStringlet<SIZE>, and SlimStringlet<SIZE>. So all would be fine – except if you want to use them generically. In that case you have to replicate all these generics on your side.

That is quite an awful burden! OTOH I can’t see a way to avoid it, if I want only valid parameter combinations to compile.

Is there a cleaner solution?

Can you move those things to the parameters of your strategy structs?

It's only better if you don't actually need the bound. It's fine to bound the struct if you need the bound, like how Cow in std::borrow - Rust has a bound on the struct because it has a field that depends on said bound.

What is the value in providing StringletBase<Kind, ...> rather than just the 4 types (SlimStringlet<SIZE>, etc)?

Generic code can just use the Deref<Target=str> trait.

I think the ALIGN parameter is also an overkill. If somebody wants to overalign a data structure, they can do this in their own wrapper type.

As long as traits and const don’t go together that is quite limiting. Just like with str, as far as reasonably possible, I want functionality to be usable at compile time.

Also optimisations like my PartialEq would need up to 16 different pairs if the types were totally disjoint.

I’ve found a way to make the generics easier. In v0.6.0, for the user’s benefit, I’ll provide additional traits, which hide the details. So you can write:

fn f<const SIZE: usize>(str: SlimStringlet<SIZE>)
where
    SlimStringlet<SIZE>: SlimConfig<SIZE>,
{}

Edit: it’s better now with v0.6.0. But I have to slim down the solution.

latest nightly has const traits now so maybe that helps

1 Like