-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Type ascription #803
Type ascription #803
Conversation
Closes rust-lang#354
``` | ||
P ::= SP: T | SP | ||
SP ::= var | 'box' SP | ... | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think P
vs. SP
could be clarified here. pat: type
is not a valid pattern (e.g., in match
) today, and the only thing that resembles that syntax is let
’s syntax. Is P
supposed to represent what goes in between the let
and =
in let <P> = <value>;
? If so, it should probably be clarified that P
is not just a normal pattern.
RFC probably should say that this replaces currently limited type ascriptions in |
|
||
// With type ascription. | ||
fn foo(Foo { a: i32, .. }) { ... } | ||
``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would we get inside foo
:
fn foo(Foo { a: Bar, .. }: Foo<Bar>) { ... }
- A variable named
a
, or - A variable named
Bar
(the current behavior)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I don't think there is a good answer. I think the least worst is to assume that users will prefer the common pattern of using the same name for both the name of the field and the new variable (fn foo(Foo { a, .. }: ...
in the current syntax), then assume that a single :
in a pattern always denotes a type, i.e., we assume Bar
is always a type (this is backwards incompatible, as you allude to). If a user wants to rename the variable, then they'd have to use a : b : _
, which is bad. I think the alternative is theoretically nicer - type ascription should be more optional, but less practical, since it is common to reuse the field name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would say that it’s probably more common to want to rename the variable than to ascribe the type. After all, we don’t ascribe the type of any struct field patterns today (because we can’t), and that isn’t causing any major problems. The Foo { a, .. }
notation is really just shorthand/syntactic sugar for Foo { a: a, .. }
, so I feel it should have a lower priority than other more fundamental parts of the syntax. Foo { a: b: _, .. }
(explicitly renaming) looks pretty bad and is not an obvious way of resolving the ambiguity, while Foo { a: a: Type }
(explicitly type-ascribing) looks OK and is fairly obvious given that the shorthand is just optional sugar.
I think that Foo { a: b, .. }
not working would be too surprising to be worth it, and the backwards-incompatibility also just makes matters worse. (Even better in my opinion would be to change struct initialisers to stop overloading :
, but that’s already been discussed (in this RFC and elsewhere).)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, on second thoughts, this makes more sense. Avoiding the backwards incompatibility/code churn is especially desirable. I think I was over-estimating how often type ascription would be used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it is more common to want renaming than type ascription, I think Foo {x: x: Foo, y: y: Bar}
is still a bit strange, would Foo {.x: Foo, .y: Bar}
look better as a sugar? (If we don't change the value binding sigil to =>
.)
Why not use an identifier and get around the similarity to struct initializers? |
I think type ascription is great, but I've never liked how everyone's always treated let z = if ... {
foo.enumerate().collect() :: Vec<_>
} else {
...
}; Or anything else really. |
@blaenk How should this be parsed? let f = foo::bar::baz; Is it the path |
@blaenk: Do you think you find |
@sfackler fair enough, I completely forgot that paths also use @jsanders Yeah, very little width is one thing, and also because it's used for explicit typing. When I look at code quickly, at a glance, I have to stop to look at the context to determine if it's an explicit type on a variable, function argument, etc., or if it's a type ascription. It becomes ambiguous to me whether something is a definition or a use of an already defined item. Note that I'm saying it's ambiguous to me, not the compiler. Also I forgot to mention something that's mentioned in the drawbacks. If we ever --- and I think it's possible given previous discussions --- introduce named arguments, I would much rather have |
@blaenk: Interesting, I guess I don't think much about the distinction between definition and use. Changing |
Yeah, I definitely don't think anyone would have much of a problem when it concerns literals, like your example shows. However, my main concern was for example with function parameters, such as this one from the RFC: foo(x: &[_], y: &[_]); From a quick glance, because foo(x is &[_], y is &[_]);
let z = if ... {
foo.enumerate().collect() is Vec<_>
} else {
...
}; Like I said I'm not trying to push But anyways, this is pretty subjective. I'm pretty flexible and I'm sure that I would get used to it without complaining, I just think it's something to think about right now before we go forward. Especially when we also consider if we would like My point is that using an alternative syntax to avoid potential confusion and to allow the possibility of using it for named arguments is insignificant compared to actually having type ascription at all, especially given the other drawback: "Interacts poorly with struct initialisers." |
since #601 (be -> become) is landing right now, "be" is free again and does not mean tail call anymore. I agree with @blaenk that "is" sounds too much like something returning a boolean due to a runtime-check (or compile-time in case of a generic?) |
My current feeling: 👍 to the RFC as written. 👎 to |
I agree with @nikomatsakis, only more so. I think that
(I also don't actually like the idea of using |
I feel like this is something that we do not have to do now or any time soon - it's a nice-to-have. |
I don't think this is a bad feature, but making expressions more complicated has more costs than benefits. (In patterns, the proposal removes some complexity.) How many
Benefits
That may be more convenient but overall usability can suffer by making expressions stranger and by encouraging longer expressions. Sometimes you can just write a literal suffix like Costs
|
I'm in favour. I have a library where being able to write: let foo =
something.bitcast() : &[u8x16]
.convert() : &[u8]
.some_method(); would be useful. The methods are defined like (I imagine this may apply to a generic On that note, my example is assuming that type ascription works inside method chains like that, which requires having |
@1fish2: I don't think that this is particularly worse than (or even very different from) the situation with My experience with statically typed languages with inference (particularly Haskell is relevant here) is that, even for applications that don't use much type ascription directly, there is a benefit to type ascription for debugging/pedagogy. If you have a type error in code that's using fairly generic functions, but you're not entirely familiar with how the types in a given API work together, you can sometimes use type ascription to figure out which function in the chain is returning something different from what you expect. That is, you can very quickly iterate with type ascription, in any arbitrary spots in a complex expression, to trigger a type error that's better than the one the compiler gave you at first, and/or to discover which step prevented type inference from resolving an ambiguity. Then you can easily yank the ascription back out of the code if you have a better fix, and it's not needed anymore. I also think that using type ascription improves readability significantly compared to the alternatives. To be frank, I would much rather have type ascription up front, with some of the weird details of UFCS syntax being the arcane you-should-learn-this-last feature, than to have no type ascription and rely on extra Type ascription is one of the simpler features to learn, and I would expect it to be something mentioned pretty early on in most tutorials and books. IMO, the existing workarounds have a much higher cost both for learners and for developers using Rust day-to-day. @huonw: I like that example. If it doesn't work, there's always this: let foo =
((something.bitcast() : &[u8x16])
.convert() : &[u8])
.some_method(); Not so elegant, but possibly less annoying than adding two extra |
Thanks, @quantheory ! That's informative, and those techniques ought to be in tutorials. Agreed, it's the volume of features that concerns me, not really this particular one. |
The alternatives mentioned (literal suffixes and @brson If we do this now maybe we have a chance to remove literal suffixes from the language? At the very least, using Maybe that's undesired, but I am feeling very confident about cleaning up those bits of syntax and semantics. |
+1 to using type ascription instead of literal suffixes. |
I'd rather have type ascription than literal suffixes, and I would rather change struct notation than lose type ascription. I didn't care about this feature until I had to work with traits... when writing tests especially you need to have the exact type of basically everything. I had a Working with closures is another example where I would like to just say
but I have to write THIS:
I can't omit even the length of the array, so when I add more stuff to it I have to manually fix it:
Type ascription is a huge ergonomics deal in current Rust until type inference is much better than what it is now |
Ditto to both. (I'd also rather change |
Do consider the ticking clock for 1.0. If we change the struct syntax now, it's backwards compatible to add type ascription post-1.0. (We could also not change struct initializers at all, at the cost of having to cut out any parts of this RFC that would cause an ambiguity.) If we remove literal suffixes, however, we would want type ascription immediately in order to replace them, which means that it would have to be implemented ASAP. Once you have type inference and type ascription for unsuffixed literals, literal suffixes are saving very little ergonomically. So I agree that in an ideal world we could just remove them as a redundant language feature. But I think it's up to the core team whether they think that they would be able to implement this RFC quickly enough to break backwards compatibility here. |
@quantheory this RFC exists because I implemented type ascription on a dare :). Only for expressions, but that seems to be what you're talking about. It would be tinier if it weren't for that weird |
Tracking issue rust-lang/rust#23416 |
@nikomatsakis Question: How does this apply to match statements? I believe this would be a very useful case when matching on |
@GBGamer what you want (I believe) is type ascription in patterns (a pattern is the general version of the thing to the left of |
Cool, thanks :) On Tue, Mar 24, 2015 at 3:28 PM, Nick Cameron notifications@github.com
"Unjust laws exist; shall we be content to obey them, or shall we endeavor |
What's the state of this? |
No implementation.
|
Type ascription seems to be cursed - everyone going to rebase rust-lang/rust#21836 disappears immediately. |
I'm not sure what the process is supposed to be for this, but the syntax chosen for this has conflicted with other desireable language features, aka https://internals.rust-lang.org/t/pre-rfc-named-arguments/3831/103 Not necessarily that we should implement that feature, but the fact that it conflicts gives credence to how the syntax migh be confusing. I'm also having difficulty seeing how the (simple) example given in the RFC:
could not be accomplished with turbofish
Not that turbofish is the greatest syntax ever, but it is in std and serves a purpose. Isn't turbosh nearly orthogonal to this feature, and if so shouldn't this feature be removed? I am worried about additional syntax appearing in the std language, making things even harder to parse for developers. |
@vitiral
Changing it would put one more nail in type ascription's coffin.
There are cases when type hint can't be provided through generic parameters/arguments, e.g.
|
I'm not sure about "coffin"... type ascription doesn't really have a coffin. It's a really great feature, and we're not likely to try and support a special-cased and specialized feature (named parameters) over a very-much wanted and very useful feature (type ascription). |
What's the state of this feature? It seems to me that this would be very useful for futures-oriented programming. Because expressions of composed futures/streams get very complex types, it would be very useful to be able to "pin" parts of an expression to expected types, both to help type inference and to get better error messages. Current mechanisms to do this ( (ping @alexcrichton, @brson as it came up in conversation with them a couple of months ago) |
@jsgf |
Closes #354
rendered