-
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 (ascription of patterns) #354
Comments
👍 Generalizing the struct Foo<T> { a: i32, b: T }
let foo = Foo { a: 1, b: 2.0 };
let Foo { a: x, b: y: f32 } = foo; |
@bjz the identifiers in the shorthand |
See also some discussion on rust-lang/rust#10502 |
What's the compelling reason for these features? I can see how type ascription could enable safer patterns, but in expressions it seems like excess syntax sugar and complexity. Benefits
The alternative is to write an ordinary Costs
Rust aims to be a production language that's predictable and reliable for security-critical and safety-critical software. Be careful about adding features from research languages like Haskell and Scala. |
Rust has tons of features from 'research languages', and is all the better for it, especially when it comes to safety and security. |
Indeed. Borrow the ideas that have proven out. |
There is an implementation in rust-lang/rust#21836 |
Type annotation/ascription is one of the oldest ideas in programming languages. |
@darinmorrison for anonymous expressions? And proven worthwhile? Do say more. Google mostly turns up info like the Scala Style Guide:
Criteria for language features should include usability, high bang/buck for the ecosystem, and not interfering with future plans. Apparently, type ascription for expressions is rarely used in Scala and accomplishes no more than using two orthogonal features together.
That seems to be a declaration for a |
@1fish2 I asked about something like this on IRC several months ago. The solution suggested to me was to add a macro to accomplish typing expressions. If we don't have support for it in the language, people will add a macro for it to individual projects, and all your arguments about confusion still apply or are made worse (naming and syntax could vary across codebases. An obvious alternative would be a standard macro that supplies the functionality, but I'm not sure if that's really a better choice than adjusting the syntax. Also, I'd be careful to note that your example with a struct is not necessarily something we expect (unless I'm a fool and those internal struct members are somehow expressions). As @huonw mentioned, we'll need to figure out if that makes sense (as a separate concern from being able to apply |
@1fish2 Yes. The idea is about as old as types themselves. It may not have been common in some of the most popular languages but that doesn't have any bearing on its utility. One strong argument in favor of allowing inline annotations is that it opens up options for making the type system more flexible, and potentially simplifying it even. If Rust ever adopts a bidirectional typing algorithm internally (I don't know what it currently uses), having these annotations will likely be important. (Some detail here and here). Top-level annotations are not always natural to use and may not even be feasible in some cases. Advocating for keeping the language simple and avoiding unnecessary features is admirable but I don't see this one as being a problem, at least not in the sense you have suggested so far. |
+1. Go for a standard macro. That avoids the problems and costs. Programmers can handle a great deal of complexity so it takes much restraint to avoid adding little features one by one that build up enormous complexity and sharp edges, like C++. Usability death by a thousand additions. |
I have seen enough people being confused about I have a hard time finding PL features that aren't as equally represented (or more so) than type ascription, for most of your "Costs" bullet points, aside from the last 3 which are a single point. I am actually curious what languages use |
-1. Using I feel like the Rust syntactic space is already becoming over-crowded, and it will be difficult to find room for future language extensions. Please avoid adding new symbolic operators unless absolutely necessary. |
And no, macros are not a solution. You cannot create a // Original implementation (bad for inference and lifetime scopes):
{
let xs: Box<[_]> = Box::new([$($x),*]);
slice::SliceExt::into_vec(xs)
}
// New implementation:
<[_] as slice::SliceExt>::into_vec(Box::new([$($x),*]))
// Slightly better if we ignore the temporary Box::new usage:
<[_] as slice::SliceExt>::into_vec(box [$($x),*])
// And type ascription:
(box [$($x),*]: Box<[_]>).into_vec() Now you tell me what's more complicated and damaging. Another data point: if we decide to replace pub fn noop_fold_where_clause<T: Folder>(
WhereClause {id, predicates}: WhereClause,
fld: &mut T)
-> WhereClause {...}
// With this nicer version:
pub fn noop_fold_where_clause<T: Folder>(WhereClause {id, predicates}, fld: &mut T)
-> WhereClause {...} Now that I think of it, that may not be the best example - |
@dgrunwald you bring up a good point: using If we could go back in time, it might make sense to avoid that mixed meaning and use (say) Off topic, but: Going forward, I don't think introducing more users of the "has value" meaning for |
@jmesmon I believe As for the current use in struct literals, I've got mixed feelings: // Out of these two, I find the JS(ON)-like syntax most pleasing:
Point { x: x, y: y }
Point { x = x, y = y }
// But here, using equal signs conveys the intention better:
Point { x: x: f32, y: y: f32 }
Point { x = x: f32, y = y: f32 } |
There's a substantial forum discussion about struct initializer syntax where that's on topic. |
@eddyb: Using |
I think using match $input { a => { let x: $type = a; x } } Although this may not actually give the same behaviour. |
@huonw that's a nice way of solving the lifetime issue, but it's still useless for coercions. |
Oh geez. Type ascription has been in the plans since near-forever, only put off because it's backwards compatible, non-critical and can be added after 1.0. I've always been bothered by others not being bothered by the obvious syntactic conflict with it that (For what it's worth (which, post-alpha, is zilch), my preferred syntax for |
I'd never seen that syntax before in the context of Rust and I kinda like it, I wish we had this discussion before the alpha (is it really too late for miracles?). Point { .x = 10, .y = 11 }
// would expand to the more verbose
{ let p: Point; p.x = 10; p.y = 11; p }
// even if that would be safe, I doubt it will be overused as the
// struct literal syntax is always shorter - except if this worked:
{ let p: Point; (p.x, p.y) = (10, 11); p }
// but why not
Point { (.x, .y) = (10, 11) } We can still change this... right? |
Which leads to a further extension - if we had HashMap { ["foo"] = bar }
// as sugar for:
{ let m = HashMap::new(); m["foo"] = bar; m } |
If you bring up named arguments, let's unify struct constructors with that. struct Point { x: i32, y: i32 }
let p = Point(x = 1, y = 2); |
I wish so, but since the alpha I've seen three reasonable syntax change RFCs (full disclosure: two were my own) summarily closed. Have any been accepted?
This is #372 :)
Yeah, C# 6 added this as well. That's actually where I got the idea for |
#372 would be a pain to analyze or trans, otherwise we would have it already. There's no real grammar issues there AFAICT, just wish we had a proper MIR... |
Nobody has explained why type ascription on expressions is desirable for Rust. How many Language design should design for usability and do usability testing. It's easy for one person to add a feature and know what it does; not easy for all future programmers to come across yet another feature in code or docs and find out what it does and how it interacts with other features. Guido van Rossum on "Language Design Is Not Just Solving Puzzles":
|
I think they have. But here it is in a nutshell: Rust doesn't have global decidable type-inference. That means that sometimes annotations or hints are necessary. Without having inline annotations, you have to use something more elaborate like these macros with let or match, which may not always work due to language semantics (@eddyb pointed out one instance of this), or may change the meaning of your program in subtle unintended ways. Annotation should be a no-op (erased to the underlying term). There's plenty of other examples of why they are useful both in theory and practice. Adding type annotations to the language would hardly turn Rust into Scala or Haskell. From my perspective, if you really believe that this would be a harmful feature, the burden should be on you to justify your position with something concrete. |
I don't actually think this proposal is terrible but I don't see the gain. Can you explain it an engineer? Please do give those examples. What concerns me is usability and learnability are under duress from a wave of feature proposals (and we need a production language that can replace C/C++ for safety-critical and security-critical software). I'm sorry, brother @darinmorrison but I tried to read your paper and had no idea what the math notation meant or how it relates to this proposal. Does "global decidable type-inference" mean a type probability cloud collapses over the whole program, yielding an eigen-type? It seems to me that limited, local inference would be more predictable and reliable. (Is there a doc on Rust type inference?)
How would adding a
I think I have. The proposal adds complexity and associated costs. It interferes with syntax for struct init and (future) default arguments. We could also do a quick usability test by showing examples to programmers and asking them to interpret. |
If type inference is decidable, it just means that for any program If type inference were global, it would try to do this across all parts of the program, even without top-level annotations on functions. This is like what the ML folks do but not how Rust works.
Well, yes. And Rust does not try to do global inference as far as I understand. But even if you restrict to some sort of local inference, it still does not mean you can avoid annotations or hints entirely. |
@1fish2 Rust currently allows specifying the type of a thing using I don't know Scala, and have no idea what connotations the feature might carry there. The syntactic conflict with |
Thanks. Here's a Scala example of type ascription:
now
or
or
which is clearer and just as concise. Anyway, for the
instead of
I don't think type ascription is a terrible thing but why pay all those costs to make a rare case more compact? The part that worries me is the drive to add features. |
See #803 |
Reopening since #803 does not cover pattern ascription. And edited the issue title to reflect that. |
Some people have commented that they would like to see more examples of why type ascription would be useful. I can't speak for all cases - in particular I don't really get fn main() {
let connection = PgConnection::establish(DB_URL).expect(&format!("Error connecting to {}", DB_URL));
use schema::companies::dsl::*;
use models::*;
let results = companies.load::<Company>(&connection).expect("error loading models");
for company in results {
// what is the type of company?
}
} I think that for company: Company in results {
} Unfortunately, that's not allowed at the moment. To a newcomer to Rust, the above seems like something you should be able to do. Why? Because you can do it for normal let statements: // this compiles fine
let company: Company = something_that_has_type_company; So why can't you do it in a if let Some(company: Company) = results.into_iter().next() In the nature of making the language more consistent, and to allow these types of sanity checks, we should allow type annotations like this on all the |
Most people do something like let () = company; which will error as long as |
@steveklabnik You're right, you can do that, and for the above example I could have done something similar: for company in results {
{
let c: &Company = &company;
}
// ...
} But why can't you just put the type you're expecting into the for loop? Again, it should be consistent. |
Another example: |
Closing in favor of #2522. |
Consider this comment deleted. Updated comment at #2522 (comment) |
Post-1.0, we would like to allow arbitrary type ascription - that is annotating any expression with a type. E.g.,
let _ = foo(x, y: Bar<int>, z);
(type ascription on the sub-expressiony
).Detail to be nailed down - precedence (probably the same as
as
).Optional extra - type ascription on patterns, e.g.
let (x: Bar<int>, y) = foo(...);
- useful when you care about the type of some part of the pattern but not others, especially when the bits you don't care about are_
.Optional, optional extra - using the ascribed types in pattern matching for downcasting (an extension of some #349 proposals).
The text was updated successfully, but these errors were encountered: