Allow _ in `impl` blocks when the generic arugment doesn't matter

Consider this:

pub struct MyTaggedData<T: Really + Complex + Trait + Bounds> {
    tag: String,
    data: T,
}

pub trait Tag {
    fn tag(&self) -> &str;
}

impl<T: Really + Complex + Trait + Bounds> Tag for MyTaggedData<T> {
    fn tag(&self) -> &str {
        &self.tag
    }
}

The Tag implementation does not care about T - it is only interested at the non-generic tag field. And yet it needs to specify the generic parameter with all it's complex trait bounds.

I don't suggest removing the trait bounds entirely from impl Trait blocks (I'm pretty sure this was already suggested multiple times in the past), but when we don't need to refer or acknowledge the generic type, it'd be nice if we can just white-hole it:

impl Tag for MyTaggedData<_> {
    fn tag(&self) -> &str {
        &self.tag
    }
}
2 Likes

People often handle this by removing the bounds from the struct, and only putting those bounds on individual impl blocks for methods that need them.

8 Likes

I want to say this is a nightly feature (and one nominated for edition 2024), but i can't find any evidence of such a feature existing now..

The general name for similar functionality is "implied bounds" but I don't know the status of anything in that space.

More specifically, it's generally considered more idiomatic to only put bounds which are structurally required on the struct itself, i.e. those which are required to name the (projected) types of the component data.

6 Likes

what about when those bounds are required to uphold a container's basic invariants? like a sorted list type, should that have an Ord bound?

No, same as how HashSet<T> doesn't require T: Hash + Eq.

The bounds should be on the methods that need it to be able to uphold the sortedness invariant.

3 Likes

Without implied bounds, the OP seems to boil down to

  • don't consider some bounds on the declaration to be well-formed constraints of the type, or
  • don't check well-formedness on some[1] implementations until used (push those errors into post-monomorphization)

  1. (more) ↩ī¸Ž

Could you elaborate on what "well-formedness" means in this context?

Well-formed types

A type is considered well-formed (WF) if it meets some simple correctness criteria. For builtin types like &'a T or [T], these criteria are built into the language. For user-defined types like a struct or an enum, the criteria are declared in the form of where clauses. In general, all types that appear in the source and elsewhere should be well-formed.

By putting the bounds on the declaration, you're communicating to the compiler that the type is not valid unless the bounds are met. Then the compiler has to enforce this at some point. To me your OP sounds like you want less things enforced, or you want the point where things are enforced to change (with or without implied bounds).

1 Like

Thanks for the explanation!

I don't think these bounds will be unenforced in any suggestion, because:

  1. They are defined on the type declaration which means that the compiler is always aware of them (even the RPC you've linked does not suggest to not enforce them - only to infer them)
  2. impl blocks are not actually instantiating the types, so the enforcement was not happening there either way - all that was happening there is that the user was forced to re-state the bounds.

My suggestion is actually weaker than RFC 2089. If we modify my suggestion's syntax to just not require the bounds (and have my original suggestion of allowing white-hole type inference as sugar on top of it), then with my suggestion this will compile - unless you uncomment the commented out line:

trait Foo {
    fn foo(&self);
}

struct Bar<T: Foo> {
    foo: T,
}

impl<T> Bar<T> {
    fn bar(&self) {
        // self.foo.foo();
    }
}

With RFC 2089 it'd compile even with the commented out line, because Bar's generic parameter T is bound to implement Foo in the declaration, and thus all impl blocks can automatically infer it. In my softer suggestion, we won't have to tell the impl block that T: Foo and the impl block won't yell at us that Bar<T> can only work if T: Foo (which I assume is what you mean by "not enforcing", but I don't consider it as such), but unlike RFC 2089 we won't be able to utilize the fact that T: Foo inside the impl block.

I'll even go farther: under my suggestion this will not compile:

impl<T> Bar<T> {
    fn bar(self) -> Bar<T> {
        self
    }
}

Under my suggestion, this will complain that the Bar<T> returned from bar is illegal because it does not know that T: Foo like Bar's trait bounds require. Even though we are inside a Bar<T> impl block, it won't do that deduction. Of course, returning Self will be just fine, because then it won't have to deduce that T: Foo.

Right, this is pretty much what I meant by the "pushing the errors elsewhere" option.

This also includes bounds that are needed in Drop, since that must match the type -- E0367.

4 Likes

Another interesting case for _ as an inferred type hole generic that just came to mind — allocator generic collections are so annoying due to needing to propagate around the A: Allocator parameter everywhere (and the concept of splitting String/str for the alloc-free subset not always working cleanly).

If routines that need to work with collections could just change <T> to <T, _>, that would decrease the burden significantly. Although, in full fairness, <T, impl Allocator> also isn't that much of a burden either, and doesn't block turbofish of the other generic parameters anymore[1].


  1. Although, unfortunately, it is still an inference breaking signature relaxation, as a fully turbofished name isn't sufficient to uniquely identify a function instantiation anymore. ↩ī¸Ž

3 Likes

I wish allocator support stabilization could just be punted on Rust getting effects or contexts or capabilities or whatever type of DI that would permit some sort of implicit diffusion of allocators down the call stack without ceremony.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.