Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC for allowing eliding more type parameters. #1196
Conversation
pnkfelix
reviewed
Jul 9, 2015
| since it becomes easier to type-hint things. E.g. the `random` | ||
| example can becomes `random(..): u8`. | ||
| - Require noting that parameters have been left to inference, | ||
| e.g. `random::<u8, ...>`. This defeats much of the syntactic point, |
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jul 9, 2015
Member
this alternative is my personal preference (though perhaps with two periods rather than three).
I see why @huonw prefers his proposal, but on the flip side: How do I now specify "these are the exact type parameters, and if another gets added, I want to be told at compile time" ? (Think of it like not having a _ => ... match arm...)
This comment has been minimized.
This comment has been minimized.
pnkfelix
Jul 9, 2015
Member
(of course one might reasonably point out that we already do not have a way to express "these are the exact type parameters" when dealing with a struct/enum with default type params" ...)
This comment has been minimized.
This comment has been minimized.
huonw
Jul 9, 2015
Author
Member
Do you have any cases in mind where it's important to know that one is specifying all of the parameters? It seems to me that most cases where adding new parameters requires external adjustment will fail to compile, but I could very very easily be wrong.
This comment has been minimized.
This comment has been minimized.
|
I assume you can only elide the last n type parameters this way? Would this interact with default type parameters? Like Felix, I think I would also prefer the dots - some evidence of the elision seems worthwhile. Generally we want this - we require it in patterns for example, so I think it is consistent. The only places we allow elision without some syntactic indication (I think) is where we elide everything - e.g., lifetime elision (one of one thing) or type inference (all type parameters). |
nrc
added
the
T-lang
label
Jul 9, 2015
nrc
assigned
huonw
Jul 9, 2015
This comment has been minimized.
This comment has been minimized.
Yeah, only trailing parameters.
I assumed that omitting a default and writing |
This comment has been minimized.
This comment has been minimized.
|
What would happen if only one // before: collect::<Vec<_>>()
[1, 2, 3].iter()
.map(|&x| x)
.collect::<Vec<_>>();
// after: collect::<Vec>()
[1, 2, 3].iter()
.map(|&x| x)
.collect::<Vec>(); |
This comment has been minimized.
This comment has been minimized.
|
The proposed elision only applies to top-level parameters in expressions, not types, so no that doesn't work. |
alexcrichton
referenced this pull request
Jul 10, 2015
Merged
std: Deprecate a number of unstable features #26914
This comment has been minimized.
This comment has been minimized.
reem
commented
Jul 10, 2015
|
+1 to this proposal, I've wanted this many times. |
This comment has been minimized.
This comment has been minimized.
|
Does it have any undesirable interactions with potential variadic generics? It is interesting, that full omission of generic parameters in brackets is currently allowed:
It looks like the alternative with |
This comment has been minimized.
This comment has been minimized.
|
I agree that it shouldn't be problematic with VG.
(FWIW, I don't think we should treat this as a bug, I like how Rust is quite flexible with "zero or more" and "allow trailing" rules, which help with macros and code-generation.) |
This comment has been minimized.
This comment has been minimized.
|
As another alternative, maybe use defaults to opt into this feature, e.g. |
This comment has been minimized.
This comment has been minimized.
Wait, in which of the above two categories are you placing default type parameters?
Attempted illustration of what I'm trying to ask about: http://is.gd/gT0ALq (At this point I am leaning towards a position that this RFC is consistent with our attitude towards default type parameters, for better or for worse. However, one might make the reasonable argument that up until now, elided things followed pretty simple rules to determine what was being elided -- I also wrote "syntactic rules" but I'm not 100% sure we can call the resolution of the type parameter defaults 100% syntactic...) |
This comment has been minimized.
This comment has been minimized.
|
@eddyb Just to clarify: Am I right that the semantics of your proposed alternative is: fn all_req<X, Y>(x: X, y: Y) { ... }
fn has_default<X, Y = i32>(x: X, y: Y) { ... }
fn can_omit<X, Y = _>(x: X, y: Y) { ... }
all_req::<char, _>(c, c);
all_req::<char>(c, i); // error: type parameter missing from instantiation
has_default::<char>(c, i); // okay
has_default::<char>(c, c); // error: `c` is not `i32`
can_omit::<char>(c, i); // okay
can_omit::<char>(c, c); // okay |
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix That is exactly what I meant, yes. I originally thought of the lifetime counterpart like this, back when struct SomeContext<'tcx, 'a = '_> {
tcx: &'a TypeContext<'tcx>,
...
}
fn compute_type<'tcx>(cx: SomeContext<'tcx>) -> Ty<'tcx> {...}The Which reminds me, there's a subtle issue with type holes: they only exist at the AST level. fn infer_vec<X, Y = Vec<_>>(x: X, y: Y) { ... }The definition above would result in the internal type representation Although, even that is suboptimal: type DoubleMe<T> = (T, T);
fn double_trouble<A = DoubleMe<_>>(...) {...}The above use of a type alias should not expand to just any |
This comment has been minimized.
This comment has been minimized.
|
Concerning this example: fn random<T: Rand, D: IntoRand<T>>(d: D) -> TIf we had the fn random<T: Rand>(d: impl IntoRand<T>) -> TThis would achieve the same purpose. I suspect that a great many of the cases that cause type parameter omission to be desirable would also be satisfied by that proposal. Just something to consider. |
This comment has been minimized.
This comment has been minimized.
I do not believe we currently allow |
This comment has been minimized.
This comment has been minimized.
|
I agree with @nrc and @pnkfelix that using |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis I was describing an issue that would arise if we just allowed My worries were unfounded, as type inference variables can still be used (with an extra instantiation step), but I wanted to document the case anyway. |
This comment has been minimized.
This comment has been minimized.
|
Yeah I've run into situations where it was necessary for the user to explicitly state one type parameter, but the rest were 'inconsequential' to the user, for example, a closure trait bound, so I did away with the idea entirely instead of requiring the user to do This would be nice. |
This comment has been minimized.
This comment has been minimized.
|
@blaenk I keep seeing these examples and wondering whether a trait method would've worked better. |
This comment has been minimized.
This comment has been minimized.
|
Yeah unfortunately I don't remember exactly what I was doing that I ran into that. But wouldn't that require me to do |
This comment has been minimized.
This comment has been minimized.
|
@blaenk No, I mean, splitting the type parameters into the type implementer aka |
This comment has been minimized.
This comment has been minimized.
This isn't obvious to me. That is, it's not obvious to me what the I'm not sure I understand the overall point of #1196 (comment). Am I correct that is basically "just" |
This comment has been minimized.
This comment has been minimized.
|
@huonw Well, |
This comment has been minimized.
This comment has been minimized.
|
In the language subteam meeting, we explored an interesting question about the interaction of this idea with defaulted type parameters. In particular, in types at least, if type parameters with defaults are omitted, this is equivalent to having typed the default value explicitly (versus creating a variable with fallback). For example, if I write this: struct Foo<T,U=T>(T,U);
...
let x: Foo<i32> = Foo(22, 'c');I will get a compilation error, because This RFC is specific to fn/method references, so in a way there is no conflict here, but the discrepancy is somewhat unnerving. It feels like type parameters lists should behave the same with respect to defaults, regardless of what they are attached to. It's not entirely clear to me which behavior is better, for that matter, when it comes to types at least. The current behavior means that (e.g.) @huonw pointed out that the alternative of requiring an explicit fn foo<T>(..., x: u64) { ... }to fn foo<T,U=u64>(..., x: U) { ... }The default here helps to ensure that an integer literal like Thoughts? Hat tip @jroesch, who brought this interaction to my attention |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis currently in the RFC it states that this sort of "mechanical fill in with Perhaps though the same confusion can arise just from using expressions? |
This comment has been minimized.
This comment has been minimized.
|
I just wanted to say This feels like bringing implicitness to a place which should be explicit. I like Rust because it forces you to be explicit about what you want in most cases. If you want type inference, you must explicitly request type inference. edit: nevermind. |
This comment has been minimized.
This comment has been minimized.
|
I might have missed something in this thread, but why would this be only for function calls in expressions? It makes the semantics of type parameter lists different in some places than others, which could be quite surprising, and confusing. Would it make sense to hold off until type ascription lands, to evaluate whether this added complexity is really needed? |
This comment has been minimized.
This comment has been minimized.
|
-1 to -1 to this RFC in general, at least for the moment. As mentioned in the proposal, we do expect general type ascription to land someday, which seems more composable than what this RFC describes while also not introducing more special cases between expression position and type position like this proposal does. At best, I'd say to postpone this RFC until after type ascription lands. |
This comment has been minimized.
This comment has been minimized.
I think the potential confusion and surprisingness of allowing it in other places is even worse: it feels natural for functions, but not so much for types, e.g. |
This comment has been minimized.
This comment has been minimized.
tikue
commented
Dec 2, 2015
I think that's because you're eliding the brackets in addition to the types themselves. On the other hand, It could also be extended to allow |
This comment has been minimized.
This comment has been minimized.
comex
commented
Dec 2, 2015
|
What about |
This comment has been minimized.
This comment has been minimized.
This does make some amount of sense... |
This comment has been minimized.
This comment has been minimized.
I'm very confused. To me, the core motivation of this RFC is to make it so that you can add new type parameters to a function without breaking existing code. Of the various alternatives that have been floated, none seem to tackle this essential goal. Type ascription seems orthogonal. |
This comment has been minimized.
This comment has been minimized.
I think eddyb's did; it just adds the onus that when adding such parameters, one must declare them via either Update: of course further discussion up above points out places where the resulting semantics may be a bit weird when comparing behavior in type constructions versus expressions (as noted #1196 (comment) )
Hmm, okay that may be true; I may have not thought fully through what @bstrie was saying |
This comment has been minimized.
This comment has been minimized.
Maybe, but we don't write |
This comment has been minimized.
This comment has been minimized.
Ohh... this really wasn't obvious from the way the RFC was worded, at least to me. I'm still hesitant though. Isn't semver meant to solve these problems? Or is this a problem for the standard libraries, which have less flexibility to bump a major version for these kinds of changes? |
This comment has been minimized.
This comment has been minimized.
|
@huonw I am against a plain But that's not enough if the type was used outside of a function, in which case, there is no way to handle the backwards compat issue, except for the cases where is actually a sensible default to use (in which case, defaulting to inference is not what you get inside function bodies). |
huonw
force-pushed the
huonw:prefix-ty-param
branch
from
31fbc39
to
b0267f8
Dec 3, 2015
This comment has been minimized.
This comment has been minimized.
|
I've now added a commit that adds API evolution as part of the motivation, since this RFC plays into that well, as @aturon has been pointing out. |
This comment has been minimized.
This comment has been minimized.
|
We discussed this a fair amount in the @rust-lang/lang meeting yesterday. The general consensus was that the proper interaction between this and fallback was unclear, but that was partly because the proper behavior of fallback itself is unclear. We didn't reach a firm decision, but as of the end of the meeting we were leaning towards:
Let me start with the reasons in favor of this RFC, and I'll discuss point 2 afterwards. I'm repeating points that have already been raised in the discussion thread here, but it's helpful to have it summarized. Basically there are two motivations:
Now, on the topic of the interactions with fallback for inference, there is growing concern from many of us that fallback is a bad idea. Experiments with implementing it revealed that there are a number of nontrivial decisions to make, and there are also strong composability concerns (basically, it's very easy to wind up with competing defaults, and there is no good way to resolve them). Defaults will also have trouble permitting forward evolution due to those same composability concerns as as well as the interaction with coercion: changing something from a concrete type to a default is potentially quite a big difference in terms of its interaction with other inference variables. Unfortunately, it seems that the compiler now accepts defaults on fns and, moreover, they act in a kind of inconsistent way. That is, if you write Some questions that arose in my mind as I typed this comment:
|
This comment has been minimized.
This comment has been minimized.
|
Thanks for taking the time to write that up, it's most appreciated! |
This comment has been minimized.
This comment has been minimized.
|
I as well didn't realize that the impetus was to allow API evolution without breaking changes, so I retract my objection. Still have a tickle in my mind like this is going to constrain us down the road, though. +1 to deprecating defaults on fns asap. |
This comment has been minimized.
This comment has been minimized.
briansmith
commented
Dec 8, 2015
How would you add a new type parameter to a function and guarantee that the calling code will break? Being able to do this is one of the key features of using a statically-typed language, especially one that is designed to use the static typing for safety-oriented programming. The proposal would be better if it were changed so that defaulting to the inferred type is only done when specifically requested, e.g.
|
This comment has been minimized.
This comment has been minimized.
jnicklas
commented
Dec 8, 2015
|
Speaking as a Rust beginner: Restricting this to expressions only seems very arbitrary to me. The distinction is quite subtle. I think this would be quite difficult to explain in documentation. I know Rust is not a language designed for beginners but even relatively proficient programmers might not be aware of the distinction between statements and expressions. Unless they've had cause to work on a compiler there isn't really any good reason to know this anyway. As such these rules seem pretty arbitrary. Is there any good reason to allow this in one place and not in any other? I don't feel like the RFC lays out why this restriction is in place. Also it's unclear from the RFC if it should be legal to elide all type parameters in a case such as If it is restricted to eliding all type parameters except the first, then that's even more arbitrary, and even harder to explain. It feels to me like this RFC is kind of a half-way solution. Either go for full-on elision or don't do it at all. |
This comment has been minimized.
This comment has been minimized.
|
@jnicklas The difference is not between statements and expressions, but between paths with explicit type parameters, in both expressions and types. This is in part affected by/possible due the fact that inside functions there is type inference (and non-const expressions can only be found in functions). But it would be very surprising to me, I would like to see I guess I'm mostly worried about littering code with explicit types that give the wrong impression about their actual number of type parameters. |
nikomatsakis
referenced this pull request
Dec 10, 2015
Closed
Document Default Type Parameter Fallback #27312
This comment has been minimized.
This comment has been minimized.
erickt
commented
Dec 14, 2015
|
For general consistency with the pattern matching syntax, I do like using |
This comment has been minimized.
This comment has been minimized.
|
I agree that this should be opt in, either using |
This comment has been minimized.
This comment has been minimized.
colin-kiegel
commented
Jan 5, 2016
|
As a compromise, you could allow ellided type parameters in two ways - say
In the future the second version could be deprecated in favour of type ascription? This way existing code does not break and the ellision does not happen silently, too. Just my two cents. :-) |
This comment has been minimized.
This comment has been minimized.
|
The lang team discussed this RFC and related issues a final time yesterday, and determined that we'd like to close the RFC for the time being, and ultimately open a new RFC that covers not only the use cases here, but also the open questions about fallback in general. I'm planning to spearhead the effort of creating a comprehensive plan here. I'll be working with various stakeholders who would like to see improvements in this space for library ergonomics. @bluss, you're one of the main people I have in mind, but if others have specific use cases, please let me know! |
huonw commentedJul 9, 2015
Rendered.