How can I prune superfluous generic bounds?

88 views Asked by At

I rely on the compiler to tell me when generic bounds on my types/impls are insufficient and which trait constraints are missing, but the compiler never tells me when my generics are over-constrained.

Is there a tool for finding generics with constraints that are unneeded by any part of the implementation?

For example, if I had some wrapper around a HashMap and a custom impl for get:

struct MyWrapper<K, V> {
    inner: HashMap<K, V>,
}

impl<K: Eq + Hash + Serialize, V> MyWrapper<K, V> {
    pub fn my_get(&self, key: &K) -> Option<&V> {
        self.inner.get(key)
    }
}

And let's say HashMap requires the key K to be Eq + Hash, but not Serialize, then it would be nice to run some tool that says, "Excuse me, but you're over-constraining your impl and you could remove Serialize if you'd like".

Are there any tools like this?

1

There are 1 answers

1
drewtato On

As far as I know, there isn't anything that does this. Here's some examples where you want traits even though you're not "using" them.

  • You are writing something that relies on marker traits. While yours requires Eq, the implementation of HashMap doesn't actually require Eq, in that code would still compile if that trait was removed. This is also relevant if you're writing an alternative thread spawner. Rust doesn't intrinsically know when something needs to be Send; the spawn function has to tell rust this is required.
  • You want to enforce bounds for the future. Adding a trait bound to your public API is a breaking change, so if the implementation could be changed in the future, it can be useful to have an unnecessary trait bound. This is also useful if you want an identical API across features or targets.
  • You're turning something into dyn Any and back. Rust isn't going to know that the value needs to preserve its trait bounds through the conversion to dyn Any.

Something that might help is to put the trait bounds on the function instead of the impl block:

impl<K, V> MyWrapper<K, V> {
    pub fn my_get(&self, key: &K) -> Option<&V>
    where
        K: Eq + Hash,
    {
        self.inner.get(key)
    }
}

This is nice if you have some trait bounds that are always needed, but many others that are only needed once or twice.