(aka how to stop rampant spread of generic parameter bounds)
TL;DR: Why can't you specify a generic struct as a bound in a where
clause on a function/struct? Or is this something that can already be achieved with nightly features?
Say we have a library that defines
pub struct VeryUsefulStruct<A, B, C, ...>
where
A: Copy + Eq,
B: SomeExternalTrait,
C: ...
{ ... }
And also a function which nicely fills in all those generic parameters and returns that new struct:
pub fn make_useful_struct()
-> VeryUsefulStruct<Aimpl, Bimpl, Cimpl, ...> {
...
}
Initially using this struct is easy:
let new_struct = useful_rs::make_useful_struct();
new_struct.do_something_useful();
But as soon as you try to pass it to a function/save it in another struct you will run into an issue:
fn accepts_useful_struct(
s: &useful_rs::VeryUsefulStruct
missing generics for struct ^^^
This error makes sense - changing the generics on the struct can change its storage, which will change the code used to access its various fields. So Rust can't just compile a one function and have it work with every possible parameter. We need to make the function generic too:
fn accepts_useful_struct<V>(s: &V)
where V: useful_rs::VeryUsefulStruct
expected trait, found struct ^^^
Now, for an average struct without generics that makes sense. But when a struct does have generic parameters, how's it different from a normal trait bound?
At this point, in order to continue, you either need to find every concrete trait implementation that make_useful_struct
returned and manually specify it:
fn accepts_useful_struct(
s: &useful_rs::VeryUsefulStruct<
useful_rs::Aimpl,
useful_rs::Bimpl,
useful_rs::Cimpl,
...
>
) {
...
}
That is definitely not very flexible. Also some implementations might even be inaccessible from outside the library.
Another solution is to copy struct's trait bounds manually:
fn accepts_useful_struct<A, B, C, ...>(
s: &useful_rs::VeryUsefulStruct<A, B, C, ...>
) where
A: Copy + Eq,
B: other_lib::SomeExternalTrait,
C: ...
{
...
}
This is slightly better. However, it requires copying code from a library (which might change), trait bounds can get massive (and they need to be duplicated for every function). It may even require extra dependencies in Cargo.toml
(if a trait in a bound comes from a private import inside the library).
A better solution could be, for example, the compiler replacing a generic struct in trait bounds with a copy of that struct's own trait bounds and generic parameters.
So my questions are:
- How difficult would the above proposal be to implement?
- Could it somehow cause inconsistencies in the type system?
- Is it something that has already been considered/is being worked on?
- What other solutions exist to this problem?
P.S. There is of course a simpler solution to this problem - the library could provide traits for it's generic structs. However, if there's only ever a single implementation of that trait, it means function definitions are needlessly duplicated
Thank you for reading to the very end!