2015-02-03 05:29:24 +00:00
|
|
|
- Start Date: 2015-2-3
|
2015-03-16 18:13:21 +00:00
|
|
|
- RFC PR: [rust-lang/rfcs#803](https://github.com/rust-lang/rfcs/pull/803)
|
|
|
|
- Rust Issue: [rust-lang/rust#23416](https://github.com/rust-lang/rust/issues/23416)
|
2015-03-05 04:27:30 +00:00
|
|
|
- Feature: `ascription`
|
2015-02-03 05:29:24 +00:00
|
|
|
|
|
|
|
# Summary
|
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Add type ascription to expressions. (An earlier version of this RFC covered type
|
|
|
|
ascription in patterns too, that has been postponed).
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Type ascription on expression has already been implemented.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-03 05:33:20 +00:00
|
|
|
See also discussion on [#354](https://github.com/rust-lang/rfcs/issues/354) and
|
|
|
|
[rust issue 10502](https://github.com/rust-lang/rust/issues/10502).
|
2015-02-03 05:29:24 +00:00
|
|
|
|
|
|
|
|
|
|
|
# Motivation
|
|
|
|
|
|
|
|
Type inference is imperfect. It is often useful to help type inference by
|
2015-02-26 05:05:31 +00:00
|
|
|
annotating a sub-expression with a type. Currently, this is only possible by
|
|
|
|
extracting the sub-expression into a variable using a `let` statement and/or
|
|
|
|
giving a type for a whole expression or pattern. This is un- ergonomic, and
|
|
|
|
sometimes impossible due to lifetime issues. Specifically, where a variable has
|
|
|
|
lifetime of its enclosing scope, but a sub-expression's lifetime is typically
|
|
|
|
limited to the nearest semi-colon.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
|
|
|
Typical use cases are where a function's return type is generic (e.g., collect)
|
|
|
|
and where we want to force a coercion.
|
|
|
|
|
|
|
|
Type ascription can also be used for documentation and debugging - where it is
|
|
|
|
unclear from the code which type will be inferred, type ascription can be used
|
|
|
|
to precisely communicate expectations to the compiler or other programmers.
|
|
|
|
|
|
|
|
By allowing type ascription in more places, we remove the inconsistency that
|
|
|
|
type ascription is currently only allowed on top-level patterns.
|
|
|
|
|
|
|
|
## Examples:
|
|
|
|
|
2015-02-12 02:08:53 +00:00
|
|
|
(Somewhat simplified examples, in these cases there are sometimes better
|
|
|
|
solutions with the current syntax).
|
|
|
|
|
2015-02-03 05:29:24 +00:00
|
|
|
Generic return type:
|
|
|
|
|
|
|
|
```
|
|
|
|
// Current.
|
|
|
|
let z = if ... {
|
|
|
|
let x: Vec<_> = foo.enumerate().collect();
|
|
|
|
x
|
|
|
|
} else {
|
|
|
|
...
|
|
|
|
};
|
|
|
|
|
|
|
|
// With type ascription.
|
|
|
|
let z = if ... {
|
|
|
|
foo.enumerate().collect(): Vec<_>
|
|
|
|
} else {
|
|
|
|
...
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
Coercion:
|
|
|
|
|
|
|
|
```
|
|
|
|
fn foo<T>(a: T, b: T) { ... }
|
|
|
|
|
|
|
|
// Current.
|
|
|
|
let x = [1u32, 2, 4];
|
|
|
|
let y = [3u32];
|
|
|
|
...
|
|
|
|
let x: &[_] = &x;
|
|
|
|
let y: &[_] = &y;
|
|
|
|
foo(x, y);
|
|
|
|
|
|
|
|
// With type ascription.
|
|
|
|
let x = [1u32, 2, 4];
|
|
|
|
let y = [3u32];
|
|
|
|
...
|
|
|
|
foo(x: &[_], y: &[_]);
|
|
|
|
```
|
|
|
|
|
2015-02-12 02:08:53 +00:00
|
|
|
Generic return type and coercion:
|
|
|
|
|
|
|
|
```
|
|
|
|
// Current.
|
|
|
|
let x: T = {
|
|
|
|
let temp: U<_> = foo();
|
|
|
|
temp
|
|
|
|
};
|
|
|
|
|
|
|
|
// With type ascription.
|
2015-02-26 19:47:35 +00:00
|
|
|
let x: T = foo(): U<_>;
|
2015-02-12 02:08:53 +00:00
|
|
|
```
|
|
|
|
|
2015-02-03 05:29:24 +00:00
|
|
|
|
|
|
|
# Detailed design
|
|
|
|
|
|
|
|
The syntax of expressions is extended with type ascription:
|
|
|
|
|
|
|
|
```
|
|
|
|
e ::= ... | e: T
|
|
|
|
```
|
|
|
|
|
|
|
|
where `e` is an expression and `T` is a type. Type ascription has the same
|
|
|
|
precedence as explicit coercions using `as`.
|
|
|
|
|
|
|
|
When type checking `e: T`, `e` must have type `T`. The `must have type` test
|
|
|
|
includes implicit coercions and subtyping, but not explicit coercions. `T` may
|
|
|
|
be any well-formed type.
|
|
|
|
|
|
|
|
At runtime, type ascription is a no-op, unless an implicit coercion was used in
|
|
|
|
type checking, in which case the dynamic semantics of a type ascription
|
|
|
|
expression are exactly those of the implicit coercion.
|
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
@eddyb has implemented the expressions part of this RFC,
|
|
|
|
[PR](https://github.com/rust-lang/rust/pull/21836).
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-03-05 04:27:30 +00:00
|
|
|
This feature should land behind the `ascription` feature gate.
|
|
|
|
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
### coercion and `as` vs `:`
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
A downside of type ascription is the overlap with explicit coercions (aka casts,
|
2015-02-26 19:47:35 +00:00
|
|
|
the `as` operator). To the programmer, type ascription makes implicit coercions
|
|
|
|
explicit (however, the compiler makes no distinction between coercions due to
|
|
|
|
type ascription and other coercions). In RFC 401, it is proposed that all valid
|
|
|
|
implicit coercions are valid explicit coercions. However, that may be too
|
|
|
|
confusing for users, since there is no reason to use type ascription rather than
|
|
|
|
`as` (if there is some coercion). Furthermore, if programmers do opt to use `as`
|
|
|
|
as the default whether or not it is required, then it loses its function as a
|
|
|
|
warning sign for programmers to beware of.
|
2015-02-12 02:08:53 +00:00
|
|
|
|
2015-03-05 04:27:30 +00:00
|
|
|
To address this I propose two lints which check for: trivial casts and trivial
|
|
|
|
numeric casts. Other than these lints we stick with the proposal from #401 that
|
|
|
|
unnecessary casts will no longer be an error.
|
2015-02-12 02:08:53 +00:00
|
|
|
|
2015-03-05 04:27:30 +00:00
|
|
|
A trivial cast is a cast `x as T` where `x` has type `U` and `x` can be
|
|
|
|
implicitly coerced to `T` or is already a subtype of `T`.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
A trivial numeric cast is a cast `x as T` where `x` has type `U` and `x` is
|
|
|
|
implicitly coercible to `T` or `U` is a subtype of `T`, and both `U` and `T` are
|
|
|
|
numeric types.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-03-05 04:27:30 +00:00
|
|
|
Like any lints, these can be customised per-crate by the programmer. Both lints
|
|
|
|
are 'warn' by default.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Although this is a somewhat complex scheme, it allows code that works today to
|
|
|
|
work with only minor adjustment, it allows for a backwards compatible path to
|
|
|
|
'promoting' type conversions from explicit casts to implicit coercions, and it
|
|
|
|
allows customisation of a contentious kind of error (especially so in the
|
|
|
|
context of cross-platform programming).
|
2015-02-03 05:29:24 +00:00
|
|
|
|
2015-03-12 05:53:06 +00:00
|
|
|
|
|
|
|
### Type ascription and temporaries
|
|
|
|
|
|
|
|
There is an implementation choice between treating `x: T` as an lvalue or
|
2015-06-10 03:11:08 +00:00
|
|
|
rvalue. Note that when an rvalue is used in 'reference context' (e.g., the
|
|
|
|
subject of a reference operation), then the compiler introduces a temporary
|
|
|
|
variable. Neither option is satisfactory, if we treat an ascription expression
|
|
|
|
as an lvalue (i.e., no new temporary), then there is potential for unsoundness:
|
2015-03-12 05:53:06 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
let mut foo: S = ...;
|
|
|
|
{
|
|
|
|
let bar = &mut (foo: T); // S <: T, no coercion required
|
|
|
|
*bar = ... : T;
|
|
|
|
}
|
|
|
|
// Whoops, foo has type T, but the compiler thinks it has type S, where potentially T </: S
|
|
|
|
```
|
|
|
|
|
|
|
|
If we treat ascription expressions as rvalues (i.e., create a temporary in
|
|
|
|
lvalue position), then we don't have the soundness problem, but we do get the
|
|
|
|
unexpected result that `&(x: T)` is not in fact a reference to `x`, but a
|
|
|
|
reference to a temporary copy of `x`.
|
|
|
|
|
2015-06-10 03:11:08 +00:00
|
|
|
The proposed solution is that type ascription expressions inherit their
|
|
|
|
'lvalue-ness' from their underlying expressions. I.e., `e: T` is an lvalue if
|
|
|
|
`e` is an lvalue, and an rvalue otherwise. If the type ascription expression is
|
|
|
|
in reference context, then we require the ascribed type to exactly match the
|
|
|
|
type of the expression, i.e., neither subtyping nor coercion is allowed. These
|
|
|
|
reference contexts are as follows (where `<expr>` is a type ascription
|
|
|
|
expression):
|
2015-03-12 05:53:06 +00:00
|
|
|
|
|
|
|
```
|
|
|
|
&[mut] <expr>
|
|
|
|
let ref [mut] x = <expr>
|
|
|
|
match <expr> { .. ref [mut] x .. => { .. } .. }
|
|
|
|
<expr>.foo() // due to autoref
|
2015-03-23 23:35:37 +00:00
|
|
|
<expr> = ...;
|
2015-03-12 05:53:06 +00:00
|
|
|
```
|
|
|
|
|
2015-02-03 05:29:24 +00:00
|
|
|
# Drawbacks
|
|
|
|
|
|
|
|
More syntax, another feature in the language.
|
|
|
|
|
|
|
|
Interacts poorly with struct initialisers (changing the syntax for struct
|
|
|
|
literals has been [discussed and rejected](https://github.com/rust-lang/rfcs/pull/65)
|
|
|
|
and again in [discuss](http://internals.rust-lang.org/t/replace-point-x-3-y-5-with-point-x-3-y-5/198)).
|
|
|
|
|
|
|
|
If we introduce named arguments in the future, then it would make it more
|
|
|
|
difficult to support the same syntax as field initialisers.
|
|
|
|
|
|
|
|
|
|
|
|
# Alternatives
|
|
|
|
|
|
|
|
We could do nothing and force programmers to use temporary variables to specify
|
|
|
|
a type. However, this is less ergonomic and has problems with scopes/lifetimes.
|
|
|
|
|
|
|
|
Rely on explicit coercions - the current plan [RFC 401](https://github.com/rust-lang/rfcs/blob/master/text/0401-coercions.md)
|
|
|
|
is to allow explicit coercion to any valid type and to use a customisable lint
|
|
|
|
for trivial casts (that is, those given by subtyping, including the identity
|
|
|
|
case). If we allow trivial casts, then we could always use explicit coercions
|
|
|
|
instead of type ascription. However, we would then lose the distinction between
|
|
|
|
implicit coercions which are safe and explicit coercions, such as narrowing,
|
|
|
|
which require more programmer attention. This also does not help with patterns.
|
|
|
|
|
2015-02-12 02:08:53 +00:00
|
|
|
We could use a different symbol or keyword instead of `:`, e.g., `is`.
|
2015-02-03 05:29:24 +00:00
|
|
|
|
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
# Unresolved questions
|
2015-02-12 02:08:53 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Is the suggested precedence correct?
|
2015-02-12 02:08:53 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Should we remove integer suffixes in favour of type ascription?
|
2015-02-12 02:08:53 +00:00
|
|
|
|
2015-02-26 05:05:31 +00:00
|
|
|
Style guidelines - should we recommend spacing or parenthesis to make type
|
|
|
|
ascription syntax more easily recognisable?
|