I don't know if this is something that was discussed somewhere already but I haven't run across it yet so I am going to put it up to discussion here:
How about we have a notion of "fully typed patterns", i.e. patterns from which the type can be fully inferred, and drop the necessity for providing the type for parameters in functions that are such "fully typed patterns"
For an example (using a axum handler, where this pattern could be useful)
As you can see, the type here is fully given by the pattern already and as such the type annotation should be redundant.
This is explicitly not proposing to drop the requirement that function parameters need an explicit type in general - only in the special case where the pattern of the parameter itself can fully specify that type. That means there should be no huge complexity cost for the type resolution process from that, as well as being fully compatible with existing code, i.e. this shouldn't be breaking.
Advantages
Less repetition in parameters of functions taking patterns
Potential disadvantages
I don't think there are any huge ones but maybe
Slightly increased compile times (?)
Slightly more complex error messages
More complex to generate rustdoc maybe, since we need to figure out the concrete type for the param?
I would love to hear your thoughts!
( as an aside: the name "fully typed pattern" is choosen arbitrarily by me and there is probably a better way to call it )
One similar idea (or extension) to this I had recently is to allow a sort of type ascription inside patterns, which would (when used in a function signature) negate the need to annotate the argument type separately.
For example:
fn foo((string, vec): (&str, Vec<i32>))
// could be written as
fn foo((string: &str, vec: Vec<i32>))
I don't know what the blocker is on type ascription in general (it seems to have stalled since 2016, if I looked correctly?), but maybe it could be extended to include this?
Well I agree that type ascription in general seems like a overdue thing, but I would explicitly exclude it from this ( if this ever happens ) and keep it separate, to avoid making this more complex than it needs to be, at least for now.
I think type ascription was mostly stalled because of the additional complexity which the team at the time didn't feel like time well spent to solve, although with the note that it should be picked up again later - might remember wrong here tho.
( although I agree that the tuple case in particular is a very natural extension to this )
I originally was in favour, but the discussion about the feature changed my mind. My position is now that having the : $ty always there is the right thing. Yes, Wrapping(x: u32)is enough to deduce that the parameter is Wrapping<u32>, but I no longer think that we should be making people do that deduction. The type should be directly clear, both for humans and things like proc macros.
Thus I would prefer -- both here and in things like statics -- to instead make more convenient construction and pattern syntax, for use in places where the type is contextually clear. (Like how we have Default::default() already, how C++ has conversions from initializer lists, or C# added target-typed new expressions.)
So, for example, rather than having
fn demo(Point { x, y }: Point<f32>) // decl
demo(Point { x, y }) // cal
if we added, say, Swift-inspired
fn demo(.{ x, y }: Point<f32>) // decl
demo(.{ x, y }) // call
We can remove a bunch of the unnecessary repetition without losing the syntatically-clear type.
I originally was in favour, but the discussion about the feature changed my mind. My position is now that having the : $ty always there is the right thing. Yes, Wrapping(x: u32)is enough to deduce that the parameter is Wrapping<u32>, but I no longer think that we should be making people do that deduction. The type should be directly clear, both for humans and things like proc macros.
Maybe that was unclear but I explicitly wasn't proposing to allow Wrapping(x: u32) - I agree that makes it substantially harder to read. I think Wrapping::<u32>(x) is reasonable though and already works today as a pattern.
I agree that _ { x, y } makes some sense, but _(data) in particular it's less obvious, since that's not actually type position, and _ is only inferred type today, not "inferred identifier".
would be an inferred function call, sorta? At that opens up all kinds of expectations that people could have that wouldn't actually make sense.
Thus I've been trying out the Swift-style let x: Ordering = .Less; style syntax in my examples (as opposed to let x: Ordering = _::Less; that's also often used) to see how it feels.
Probably not worth trying to solve that bikeshed in this thread, though. The important part of my post is the "infer in the pattern or expression, keep the type" direction, not the syntax for what that should look like.
I like this a lot but I think there's one problem that it wouldn't solve, which is that sometimes you run into really complex types and it would be nice if you could localize the ascription to each variable individually. For example something that I'm working on involves parameter types like this:
If you don't understand why I hate : ascryption everywhere so much, think what happens if you write if let Ok(Foo:Bar(1), _) = foo { when you meant if let Ok(Foo::Bar(1), _) = foo {. Even worse when it isn't a parse error if let Ok(Foo:Bar, _) = foo {.
would these issues still exist if we choose another character or keyword, let's say, idk, fits or something ( or we might be able to repurpose @ and just extend that syntax maybe? )
That would remove the ambiguity in
if let Ok((Foo:Bar(1),_)) = foo { ... }
// vs
if let Ok((Foo fits Bar,_)) = foo { ... }
// or
if let Ok((Foo @ Bar,_)) = foo { ... }
and would probably (idk, haven't done much with the compiler (yet), you are probably more qualified than me to judge something like that ) enable way easier compiler errors / warnings.
I'm in favor of a different syntax cause you'd probably need it for named-field struct literals anyways.
I submit: Ok(x: <i32>), where the ascribed type is wrapped in angle brackets. I think it would sidestep most path issues while still reusing existing Rust syntax. Downside is that it could still be malformed as a type with generics (Ok(foo::<Bar>), but I think that would always be a parse error in practice (no parameter list, and a lowercase type is unlikely).
But I like pretty much any syntax as long as I can insert & remove entries in a pattern without editing in 2 places.
I am partial towards the pat.as<Type> syntax because it is the closest to existing syntax while also being far removed from allowed syntax that will make it easier to deal with. But there's something to be said about maintaining the symmetry with let bindings and fn arguments. I think that allowing : only in top-level patterns is a reasonable compromise. It is also the syntax that I've imagined if we were to ever have anonymous enums:
fn foo() -> Foo | Bar {
if rand() > .5 {
Foo
} else {
Bar
}
}
fn main() {
match foo() {
foo: Foo => {}
bar: Bar => {}
}
}
If are to ever have that, then it would make sense for the same syntax to be used to aid inference and to select the implicit enum variant.
i think a good few of the (admittedly harrowing) issues @ekuber has solved have do with generalising type ascription to all patterns, which i don't think is what is this thread is/should be about:
this feature can be constrained just to places where colons are already accepted after patterns, so no match arms, no matches! macro, etc. the only places this syntax would be accepted (currently) is:
if this syntax is (understandably) deemed too problematic wouldn't an even simpler version be less controversial?
// no generics, so just name is enough to fully infer:
struct InferByName {
pub x: f32,
pub y: f32,
}
fn do_something(InferByName { x: f32, y: f32 }, num: usize);
this could even allow more elaborate examples without extending syntax. all this would require is making function parameter types optional where simple inference can deduce the type by itself: