rfcs/text/0803-type-ascription.md

227 lines
7.7 KiB
Markdown
Raw Normal View History

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)
- Feature: `ascription`
2015-02-03 05:29:24 +00:00
# Summary
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
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
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:
(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: &[_]);
```
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-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.
@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
This feature should land behind the `ascription` feature gate.
2015-02-03 05:29:24 +00:00
### coercion and `as` vs `:`
2015-02-03 05:29:24 +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.
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.
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
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
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
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.
We could use a different symbol or keyword instead of `:`, e.g., `is`.
2015-02-03 05:29:24 +00:00
# Unresolved questions
Is the suggested precedence correct?
Should we remove integer suffixes in favour of type ascription?
Style guidelines - should we recommend spacing or parenthesis to make type
ascription syntax more easily recognisable?