Why aren't we allowed to skip type parameter here?

In this code:

fn two_type_params<A, B>(b: B) {}

fn main() {
    two_type_params::<String>(100_i32); // Error: expected 2 generic arguments

    // I understand we should do this.
    // But why aren't we allowed to skip the second type parameter? 
    // We are making the compiler infer for us so the compiler should already have this information. 
    two_type_params::<String, _>(100_i32); 
}

P.S. Sorry if this should be asked in the internals forum instead, unsure which is more relevant.

Well, because it takes two generic parameters, not one.

1 Like

A related question is why we cannot specify/provide any other generic arguments on functions that take impl Trait arguments. I’m not aware of any good reason why either of that should be disallowed, but currently rustc is like that. Proposals to change this are indeed a better fit on the internals forum, asking why things are the way they are (which you did) can make sense on users, too.

A quick search didn’t find me any previous discussion on internals (regarding leaving out inferred trailing generic arguments) but I’d find it very surprising if there really aren’t any prior discussion posts.

2 Likes

One reason against allowing you to skip trailing generic arguments (and having them inferred) could be inconsistencies when compared to types with default generic arguments.

struct S<A, B = u8>(A, B);

fn main() {
    let s = S::<u8, _>(1, 2);
    type_of(&s);
    type_of(&s.1);
    let s = S::<u8>(1, 2);
    type_of(&s);
    type_of(&s.1); 
}

fn type_of<T>(x: &T) {
    println!("{}", std::any::type_name::<T>());
}
playground::S<u8, i32>
i32
playground::S<u8>
u8

(playground)

here, S::<u8, _> and S::<u8> clearly don’t mean the same thing.

5 Likes

This makes sense. I've found some discussion about a similar issue here:
Tracking Issue for RFC 213: Default Type Parameter Fallback · Issue #27336 · rust-lang/rust · GitHub

@steffahn raises a great example, although I think it still makes sense even if there aren't those kinds of edge cases. One of the things rust strives for is clarity and explicitness in its syntax, which is valuable because the more implicit stuff there is going on in the background, the likelier it gets that something will stop working in ways that are hard to understand. If I saw let x: T = T::new(123);, I would assume that T is not generic. Similarly, if I saw the code in your original post, I would assume that it's generic with one parameter. If later on I run into a compilation error relating to those generic types, it would be very cryptic to me because my mental model has strayed away from what's actually going on. By forcing the syntax to describe what is happening, it's a lot easier to understand code without needing to look at declarations in other places.

I think what you say make sense in the large, but I'm uncertain about where the "explicitness" line is/should be drawn. Given that we already have type inference in a large amount of places + default type parameter values, the same example you give x: T = T::new(123) could already very well be generic for both T and/or new, without being obvious.

Being allowed to omit type parameters in trailing positions seem quite "in line" with what we have now (backwards compatibility and edge cases aside, of course).

The parallel I might draw is instead to patterns, where you have to opt in to "and whatever else, I don't care" by writing ...

So I could see a potential feature where foo::<Bar, ..> also does the "and _ for everything else" like happens in patterns.

Maybe you find it obvious, but not everyone may, especially people who are trying to read someone else's code. It is not universal, either: in the case of one API, omitting the last type parameter might be relatively straightforward to understand, but in other cases, it may not be so at all. If for example, if the relation between type parameters and arguments is not one-to-one, it may be hard to tell which argument the specified type parameter(s) correspond(s) to.

As always, this is yet another case where we really should not be optimizing for ease of writing at the cost of readability.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.