1

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?

cdhowie
  • 158,093
  • 24
  • 286
  • 300
  • 1
    I'd have thought perhaps there was an "`unused_generic_bound`" or similar Clippy lint for this, but searching https://rust-lang.github.io/rust-clippy/master/, I came up empty. If no existing tooling supports this, consider writing/requesting a lint at https://github.com/rust-lang/rust-clippy/issues. – U007D Mar 08 '23 at 01:12
  • 2
    This is little problematic because sometimes, especially with unsafe code but not only, you want bounds that aren't necessary. – Chayim Friedman Mar 08 '23 at 11:24
  • 1
    This was suggested as an improvement to Clippy in [2018](https://github.com/rust-lang/rust-clippy/issues/3214), but the conclusion there was that it would require analysis that was too complicated for clippy and it got referred to a separate issue with the [compiler](https://github.com/rust-lang/rust/issues/50927) instead... where it has languished. – Roger Filmyer Mar 08 '23 at 18:38
  • @RogerFilmyer I think this is a slightly different problem. The link you sent seems to be for unused `impl`s, not over-constrained generics – Mitchell Turner Mar 09 '23 at 20:07

1 Answers1

1

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.

drewtato
  • 6,783
  • 1
  • 12
  • 17
  • I'm not completely convinced by those scenarios--I'd argue that the trait bound wouldn't be needed on the specific `impl` where it's not "used", but assuming there are situations you'd want to add extra constraints, you could have an `allow` exception for the linter. As @U007D points out above, the lint doesn't need to be on by default, but could be an optional Clippy lint. – Mitchell Turner Mar 08 '23 at 06:08