Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions text/0803-type-ascription.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,24 +172,20 @@ 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`.

The proposed solution is that type ascription expressions are rvalues, but
taking a reference of such an expression is forbidden. I.e., type asciption is
forbidden in the following contexts (where `<expr>` is a type ascription
expression):
The proposed solution is that type ascription expressions are lvalues. If the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this phrasing kind of odd, though I think I agree with the intention.

It seems odd to say that <expr>: T is always an lvalue. For example, is foo(): T an lvalue? The only way I can make sense of that is if <expr>: T puts <expr> into a temporary (when it is an rvalue) and then is equivalent to referencing that temporary.

My original thought was that <expr>: T would be an lvalue if <expr> is an lvalue, and an rvalue if <expr> is an rvalue. Moreover, there would be an additional rule that when <expr>: T appears in a ref context, the type of <expr> and T must match exactly (though, strictly speaking, I think it is ok to upcast if <expr> is an rvalue).

That said, I think that the "temporary interpretation" I gave above and my interpretation behave the same way from an end-user point of view. For example, if you write &(foo(): T), and you consider foo(): T to be an rvalue, then this will create a temporary with the enclosing temporary scope and store foo() in it. But if you take the first interpretation, it will also create a temporary and store foo() in it, and presumably the lifetime of that temporary would be the same (though we would probably want to adjust the temporary lifetime rules to "see through" <expr>: T).

Anyway, just for reference, let me define my terms a bit:

  • lvalue: something that can be assigned to, e.g. x, x.f
  • rvalue: something that cannot be assigned to, e.g. x()
  • lvalue/ref context: something where you are taking the address, e.g. <expr> in &<expr> or in a match, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: I realized that since lvalue controls what you can assign to, it definitely seems wrong to say that type ascription is always an lvalue, since that would permit foo(): T = 5.

I prefer just saying that it is an lvalue/rvalue based on the underlying expression, and that x: T = 5 is a reference context (and hence this annotation is basically an assertion that the type of x is T).

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This <expr> renders funny. You should add verbatim quotes.

type ascription expression):

```
&[mut] <expr>
let ref [mut] x = <expr>
match <expr> { .. ref [mut] x .. => { .. } .. }
<expr>.foo() // due to autoref
<expr> = ...;
```

Like other rvalues, type ascription would not be allowed as the lhs of assignment.

Note that, if type asciption is required in such a context, an lvalue can be
forced by using `{}`, e.g., write `&mut { foo: T }`, rather than `&mut (foo: T)`.


# Drawbacks

More syntax, another feature in the language.
Expand Down