-
Notifications
You must be signed in to change notification settings - Fork 1.5k
ref
parameters, arguments, returns and let
returns
#5434
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
base: trunk
Are you sure you want to change the base?
Conversation
Co-authored-by: Geoff Romer <gromer@google.com>
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'm still reviewing, but I wanted to get a few of these comments out early in case they're worth discussing this afternoon.
optional `ref` or `let` between the `->` and `auto`. `-> auto` continues to | ||
return an initializing expression, `-> let auto` returns a value expression, | ||
and `-> ref auto` returns a durable reference expression. | ||
- Using `=>` to specify a return continues to return an initializing |
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 there's a strong case to be made for having =>
deduce the expression form, because that makes(fn => expr)()
a drop-in replacement for expr
. I'd be fine with leaving this as an open question, but I'd rather not resolve it (particularly not in this way) without considering that alternative.
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.
There is an issue that if we return something other than an initializing expression, we probably need the parameters to be marked bound
. I added some text saying that.
```carbon | ||
fn F(ptr: i32*) { | ||
// A reference binding `x`. | ||
let ref x: i32 = *ptr; |
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'm realizing that -> let
really encourages the mental model where let
means immutable (or at least by-value), but as noted elsewhere, I think that means we should choose a different syntax. If we don't, then I don't know, maybe allowing ref
as a statement introducer would be desirable as well.
proposals/p5434.md
Outdated
|
||
### `ref`, `let`, and `var` returns | ||
|
||
The return of a function can optionally be marked `ref` or `let`. These control |
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'd like to suggest an alternative:
- The return of a function can optionally marked with
ref
orval
, which causes the function call to be a reference or value expression (respectively). - A binding pattern can optionally be marked with
ref
orval
, which causes name expressions that name the binding to be reference or value expressions (respectively). Note that this gives us a way to declare a value binding that's nested inside avar
.
That way there's a clear and consistent parallel between ref
and val
:
- Each can be used as a modifier on a binding pattern or function return declaration, and nowhere else.
- Each causes uses of the declared entity to have a particular category.
- Each is an abbreviation of the name of that category.
- One is the implicit default for bindings inside
var
, and the other is the implicit default for bindings outsidevar
.
Even the ways the parallels break down are illuminating, because they reflect the asymmetries of the underlying model of expression categories:
ref
on a binding pattern constrains the category of the scrutinee, whereasval
does not, but that reflects the fact that there are no conversions from other categories to durable reference.var
is a modifier on->
but acts as an independent operator rather than a binding modifier in patterns, but that reflects the fact that matching an initializing expression has side effects, and does not propagate its category to the subpattern's scrutinee.
This also preserves the current simplicity of let
: it's solely a pattern introducer, with no connection to expression categories or mutability.
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 forgot to update here but meant to -- I think roughly this idea is what I wrote up in the leads issue here: #5522 (comment)
(I think I misunderstood it at first to be something different, but in a live discussion Geoff corrected me.)
Flagging that here to connect all the dots.
proposals/p5434.md
Outdated
Mirroring the [paren](/docs/design/pattern_matching.md#tuple-patterns) and | ||
[brace](/docs/design/pattern_matching.md#struct-patterns) pattern forms, we also | ||
support paren and brace return forms. Every element of these forms starts with |
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.
But where does that leave "tuple literal" and "tuple pattern", which are neither types nor forms? Tuple literals in particular seem easy to confuse with tuple types, and possibly also with paren forms. Also, why would "tuple forms" and "tuple types" not be sufficiently distinguished by the fact that they have different head nouns?
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.
Made a moderately thorough pass over this to get a good background for the relevant leads issues and wanted to leave the somewhat minor editorial comments as I went.
Overall, just wanted to also say this is really awesome to pull together considering how far reaching, and how much ambiguity we need to leave to break off even this "small" of a chunk.
I do have some meatier thoughts tied up in theeads issues, but maybe best for a live discussion as I suspect somewhat I am still missing clarity and context on some parts.
### Type completeness | ||
|
||
Not a change by this proposal, but note that our existing rules will require the | ||
type in a `ref` binding to be complete in situations where it would not need to | ||
be if you were using a value binding with a pointer type instead. We may need to | ||
change this in the future to match C++ which treats reference types like pointer | ||
types for completeness purposes. |
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.
My understanding is that while we'll need completeness before we would for a pointer, much like a value binding for a type, declarations of the binding still wouldn't require completeness? Worth mentioning that the result here is expected to have the same level of incompleteness support in ref bindings as value bindings?
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 the text is technically correct as written. I can understand that it might not be as clear as it could be, but I'm having trouble figuring out how to address the points you make without introducing more confusion.
The situation is:
- The type in bindings is sometimes required to be complete, for example in definitions but some cases for declarations as well.
T*
is considered complete even whenT
is not.- It is true that
T
in a value binding will generally be required to be complete in the same situations as aref
binding. However, a value binding forT
is not a substitute for a reference binding forT
, while a value binding forT*
is much more so.
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
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.
Opening up a more substantial syntax discussion. If this ends up contentious, happy to pop it into an issue for leads, but figured it was worth checking if there is alignment here first.
proposals/p5434.md
Outdated
A function may have multiple returns, each with their own marker, by using a | ||
paren or brace compound return form. | ||
|
||
```carbon | ||
fn ParenReturn() | ||
-> (->let bool, ->ref i32, -> C) { | ||
return (true, global, {.x = 3}); | ||
} | ||
|
||
fn BraceReturn() | ||
-> {->let .a: bool, | ||
->ref .b: i32, | ||
-> .c: C} { | ||
return {.a = true, | ||
.b = global, | ||
.c = {.x = 3}}; | ||
} | ||
``` |
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 was struggling a bit with the heavy usage of the ->
symbol in these, and I discussed an alternative syntax with @josh11b in the open discussion that seemed promising.
Rough sketch of the syntax adjustment from that discussion:
// `->` begins a "return form", whatever the right word
fn F() -> <return-form>;
// Within any return form, there are N syntactic overrides where the thing following
// the arrow *does not* form a type expression
fn Let() -> let <type-expr>
fn Ref() -> ref <type-expr>
fn Var() -> var <type-expr>
fn Paren() -> ( <return-form>, ... )
fn Brace() -> { .<id>: <return-form>, ... }
// Otherwise, implicit `var`:
fn Other() -> <type-expr>
Basically, (
and {
are always compound return forms unless they are explicitly placed in a type expression using let
, ref
, or var
. This would make compound returns the default, which is a not-small change. But as I have thought about this proposal and the consequences, I think it is a reasonable if not preferable default, and it provides a nice way to organize the syntax.
There is also a minor change to put the modifier let
or ref
after the :
in the brace form. I don't feel strongly about that either way, but seemed slightly more consistent this way as this :
is a very different kind of :
.
With this, I think these examples would change as follows:
A function may have multiple returns, each with their own marker, by using a | |
paren or brace compound return form. | |
```carbon | |
fn ParenReturn() | |
-> (->let bool, ->ref i32, -> C) { | |
return (true, global, {.x = 3}); | |
} | |
fn BraceReturn() | |
-> {->let .a: bool, | |
->ref .b: i32, | |
-> .c: C} { | |
return {.a = true, | |
.b = global, | |
.c = {.x = 3}}; | |
} | |
``` | |
A function may have multiple returns, each with their own marker, by using a | |
paren or brace compound return form. | |
```carbon | |
fn ParenReturn() -> (let bool, ref i32, C) { | |
return (true, global, {.x = 3}); | |
} | |
fn BraceReturn() | |
-> {.a: let bool, | |
.b: ref i32, | |
.c: C} { | |
return {.a = true, | |
.b = global, | |
.c = {.x = 3}}; | |
} | |
``` |
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.
A slight amendment to this from open discussion with @zygoloid and @josh11b --
Whenever we have a paren form or brace form with all implicit (IE, no let
, ref
, or var
keywords), we don't form a compound return, the paren or brace form itself is just a <type-expr>
and the var goes outside of it. This basically pushes the implicit var
to the outer most layer it can be.
This gives it the important property that a type T
that is maybe an alias for a tuple type and an explicitly written tuple type as (i32, i32)
have the same behavior -- they both are var, neither is (var i32, var i32)
. If the user wants explicitly separate return forms despite all the forms being var
, they can use the explicit var
keyword to achieve this.
Co-authored-by: Chandler Carruth <chandlerc@gmail.com>
ref
instead ofvar
or the default. It will bind to reference argument expressions in the caller and produces a reference expression in the callee.ref
binding can't be rebound to a different object.addr
, and is not restricted to theself
parameter.ref
binding, like a value binding, can't be used in fields of classes or structs.self
ref
parameters are also marked withref
.ref
,val
, orvar
. These control the category of the call expression invoking the function, and how the return expression is returned.ref
binding isnocapture
andnoalias
.bound
.