Skip to content
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

Change the syntax of struct literals to use = #65

Closed
wants to merge 1 commit into from

Conversation

nrc
Copy link
Member

@nrc nrc commented May 4, 2014

Seeing that far-reaching and controversial syntactic changes seem to be in fashion at the moment, here is my pet peeve formulated as an RFC.

@nrc
Copy link
Member Author

nrc commented May 4, 2014

Also makes it easier for tools to distinguish struct definitions from struct literals.

@nrc
Copy link
Member Author

nrc commented May 4, 2014

Actually, one alternative I didn't think of earlier is that we could reverse the order of field and variable in patterns, so we would write field = var in struct literals and var = field in patterns. That seems a bit more logical but less symmetric.

@SiegeLord
Copy link

Also makes it easier for tools to distinguish struct definitions from struct literals.

I'm not sure what you mean, the difference is the presence of the struct keyword or being inside an enum. The symbol browser for Geany has no trouble picking out all struct definitions and ignoring struct literals.

I disagree with this RFC because it breaks the symmetry between struct declaration, pattern and initialization. The pattern syntax of S{ field = binding } is particularly confusing given the usual directionality of =, and reversing the order breaks the symmetry even further.

@huonw
Copy link
Member

huonw commented May 4, 2014

This possibly causes some confusion:

for foo in S { x = 1 } { y = 2 }

There's two possible parses:

for foo in (S { x = 1 }) { y = 2 }

// or

(for foo in (S) { x = 1 })
{ y = 2 }

@nrc
Copy link
Member Author

nrc commented May 4, 2014

@SiegeLord I'm not saying there is a problem, just that it would be even easier. I would be easier because you currently you need context - are you surrounded by an struct/enum block. With the proposed change, you don't need that context.

This only breaks the symmetry between declaration and (pattern and initialisation). I think that is a good thing because they are very different concepts. The declaration is { name: type } whereas the pattern/initialisation are { name: expr }. So yes, we lose symmetry, but I think we are losing bad, that is misleading symmetry, because two things look the same which are semantically very different.

Note that in exchange we get symmetry with variable assignment and field assignment. In the first case because we declare variables in the form x: T and initialise them with x = expr, which matches my proposed syntax. In the second case because when setting an individual field we use s.f = expr not s.f: expr.

@nrc
Copy link
Member Author

nrc commented May 4, 2014

@huonw I think this is solvable by generalising struct initialisers. That is, we always use the second parse and unify block syntax with anonymous record initialisers - all assigned to free variables in the block end up as forming a record which is the result of the block and which must be unified with the contents of the named struct. This may be bordering on the crazy, but we would need to solve this problem if we allow generalised type ascription or for initialising virtual structs if we go down that road.

Put another way, the fact that we are parsing struct literals based on whether or not there is a : as the second token feels like it is fragile anyway and we should fix it.

@dobkeratops
Copy link

maybe wild suggestions... I realise these aren't pretty, but here goes..

S{ .a=expr, .b=expr..} // like C99 designated initializers
S{ a=>expr, b=>expr ...} // a structure is a bit like a mapping of identifiers => values..
how about "mut x = ..." as opposed to "let x = .." to differentiate variable mutation from initialisation

or use := somewhere - either for variable mutation ? or for field initialisation.(unfortunately it would be the opposite of 'go' if it was mutation.)

Is local variable mutation the less likely case- with rusts emphasis on declaring things initialised , & expression oriented syntax? or would it annoy people from other languages too much. (I don't think it would annoy me, I appreciate the ideas that mutation should stand out, and creating initialised should be more common)

Of all those I think I prefer 'mut x=..' but don't know what other problems it would might cause..

if mutation was separated from initialisation, would it make it easier to do keyword arguments in any future version?

..or is it possible that less ambiguity between a block with assignments and a struct initialiser would let you just write field initialisers without having to specify the struct name..., making it easier to use structs inplace of keyword arguments. (don't need to write the struct name, it gets implied from the context..)
i gather the language used to have anonymous structs , but those caused problems, could those be solved..

@SiegeLord
Copy link

So yes, we lose symmetry, but I think we are losing bad, that is misleading symmetry, because two things look the same which are semantically very different.

This symmetry exists in every other type today, so it's always 'misleading'. I don't find it to be a problem at all.

Note that in exchange we get symmetry with variable assignment and field assignment

I don't think those things are related enough to field initialization to require symmetrical syntax. In return for this tenuously important symmetry, we get a confusing order for struct patterns which I think more than outweighs any gains by this change.

I should also note that keyword arguments would have a natural correspondence to struct fields, and thus could use : as well (at the call site).

@pcwalton
Copy link
Contributor

pcwalton commented May 4, 2014

Generally, one of Rust's principles is that declaration follows use. I'm not a huge fan of this change because we'd be losing the symmetry between declaration and initialization. Furthermore, : seems to be more common in other popular languages (JavaScript, Go, Python), though there are some counterexamples (C#, ML).

@pczarn
Copy link

pczarn commented May 4, 2014

I don't like either of the proposed pattern syntaxes. I believe the meaning of @ should be extended so that we can write var @ field in struct patterns.

I happen to like this syntax proposal. Since we are starting to mention other languages, take a look at Lua.

@nrc
Copy link
Member Author

nrc commented May 4, 2014

@SiegeLord @pcwalton I don't agree that in Rust "declaration follows use" or that "This symmetry exists in every other type today", in fact structs are pretty much the exception:

tuples - yes, declaration follows use, but there are no names to use in the declaration or use, so hard to see how you could do otherwise
pointer types - no, decl: @T, &T, ~T, use *T (as opposed to C)
enums - no, decl: keyword name { names }, use: one of the names
functions - no, decl: keyword name ( names: types ), use name ( values )
traits - no, decl: keyword name { fns }, use name (ok, that is a bit tenuous)

@nrc
Copy link
Member Author

nrc commented May 4, 2014

@pcwalton I would note about other languages that those which use : tend to have a history or strong bias towards dictionary data structures or other key/value pairs, so the syntax key: value gets adopted for use in structs with fields. Languages without that history/bias have been more likely to use =. Rust is in that second category - we don't use : for hashmaps, etc. so it just seems out of place to.

@pcwalton
Copy link
Contributor

pcwalton commented May 4, 2014

You create a reference with &, so declaration follows initialization
there. Also i8, u8, etc. mirrors 0i8, 0u8—people in my PR seemed to
like that symmetry. :)

On Sunday, May 4, 2014, Nick Cameron notifications@github.com wrote:

@pcwalton I would note about other languages that those which use : tend
to have a history or strong bias towards dictionary data structures or
other key/value pairs, so the syntax key: value gets adopted for use in
structs with fields. Languages without that history/bias have been more
likely to use =. Rust is in that second category - we don't use : for
hashmaps, etc. so it just seems out of place to.


Reply to this email directly or view it on GitHub.<
https://ci4.googleusercontent.com/proxy/lBWTul7rF5By35Q9g1L9qvhyxzMuE7ZsQtRJg4KwukDTr_mMsIVFw7NQcEvHhZpp93qneTr_zwIs_jeOshXkDXN22K0w0wp8AAa_V4AdB13sKLHABNE5zOZUhDXrBEAfkgodZlRyqEWqH9eE1DC6XzZc4I-dz9cur31BbfVfzxpaiOCRWNUnQW2I0W1jjT4qNnPxd1P5P0sPc6M2s7e7oqUWzD7G_EGB7SeV8R3EUH1XC8-K-cXOBW0e7KXN3DrikP9An_KY9ZC6eT6c2DUYJTB0KZhj=s0-d-e1-ft#/~https://github.com/notifications/beacon/157897__eyJzY29wZSI6Ik5ld3NpZXM6QmVhY29uIiwiZXhwaXJlcyI6MTcxNDg1MzgyMCwiZGF0YSI6eyJpZCI6MzEzODk1NDB9fQ==--97dd615f8596e2210b46b51fe479941a84608280.gif

@nrc
Copy link
Member Author

nrc commented May 4, 2014

@SiegeLord I do not think the connections are "tenuous". Field assignment is setting one field of a struct, struct initialisation is setting all the fields of a struct. Variable initialisation is initialising a piece of memory, so is a field-entry in a struct literal.

@pcwalton
Copy link
Contributor

pcwalton commented May 4, 2014

What I really meant was "the type syntax follows the initialization syntax". Unfortunately the common phrase for that is "declaration follows use", which is not really accurate.

This applies for functions as well in Rust: the type syntax is fn(int) -> int, while the initialization (declaration) syntax is fn foo(int) -> int. Likewise, the type syntax for closures is |int| -> int, while the initialization syntax is |x: int| -> int { 3 }.

tuples - yes, declaration follows use, but there are no names to use in the declaration or use, so hard to see how you could do otherwise

Well, ML forgoes this, in an explicit rejection of type-syntax-follows-initialization-syntax: the type syntax for a tuple int * float, while the initialization syntax is (0, 0.0). The ML syntactic tradition eschews type-syntax-follows-initialization-syntax, but I think the C syntactic tradition has really embraced it.

@nrc
Copy link
Member Author

nrc commented May 4, 2014

OK, & is a fair point. But other pointer types are (or going to be) using box for initialisation, so we are losing the symmetry there too. I think that while there is some nice symmetry around numeric types and suffixes, it is of a different kind than the one for struct literals.

Oh, wow, the ML syntax is certainly different :-p

Even in the C tradition, it has become weaker with time. C was very strongly about type declarations following use. C++ less so (e.g., not requiring the struct keyword to initialise a struct, prefering T* var rather than T *var, etc., syntax for method pointer types).

@cloudhead
Copy link

This is one of the first things that surprised me with Rust. I expected the assignment to work with =, like in C and Haskell (records). Partly because : seemed be the type declaration operator, the equivalent of :: in Haskell.

@lilyball
Copy link
Contributor

lilyball commented May 6, 2014

I disagree with this proposal. Variable assignment is, well, assignment. But fields in struct initializers are declarations of value, not assignments of value. I also agree with @pcwalton about type-syntax-follows-initialization-syntax.

@bstrie
Copy link
Contributor

bstrie commented May 6, 2014

I'm mostly indifferent to this proposal. I'd be happy with either today's Foo { x: 1 } or this proposal's Foo { x = 1 } (not a fan of @dobkeratops' alternative proposals, though).

The ambiguity mentioned by @huonw is a legitimate concern. Please add a mention to this in the RFC, along with your proposed solution.

Ultimately it seems that the fate of this RFC hangs on the long-hypothesized feature of generalized type ascription. Depending on how that turns out, this may be a genuine backwards-compatibility concern. I vote that we suspend judgment on this RFC until an RFC for generalized type ascription is written up and discussed.

@lilyball
Copy link
Contributor

lilyball commented May 6, 2014

@bstrie What's the backwards-compatibility concern with type ascription? The only potential issue I can think of is a slight ambiguity when parsing Foo { x: because in theory this could be a type-ascription applied to a variable x at the start of a block. Is that what you're talking about? It seems like a rare edge case that I'd personally be happy ignoring (if you truly want to use type ascription on a variable inside a block, which is rather niche, you can wrap it in parentheses). This case is significantly rarer than wanting to do assignment at the start of a block (Foo { x =), to the point where I'd actually be surprised if anyone tries to write code that hits this case.

Granted, having any ambiguity at all is bad from an idealistic standpoint, but I think this is a rather minor case.

@bstrie
Copy link
Contributor

bstrie commented May 6, 2014

@kballard , I don't know what the backcompat concerns are, nor can I, because nobody's ever written up a proposal for it. I'm not comfortable claiming that a new feature probably won't make our grammar ambiguous. And if generalized type ascription does introduce an ambiguity with our current struct literal syntax (again, I have no idea!), then I personally see no philosophical reason to be against this proposal.

@lilyball
Copy link
Contributor

lilyball commented May 6, 2014

@bstrie Hmm, ok. This Foo { x = 1 } proposal has a legitimate parsing ambiguity; struct literals currently only parse as struct literals because they look ahead past the { for identifier ':'. If : changes to = this becomes indistinguishable from assignment to a mutable variable inside a block.

@dobkeratops
Copy link

..and if thats the case, throwing the idea out there, might disambiguating mutation be the lesser evil (:=, F#'s <- ..or perhaps mut expr=expr...
And add loop return values (break expression) to increase the cases where you can do things by initialisation instead of mutation

@tbu-
Copy link
Contributor

tbu- commented May 6, 2014

Changing this would lose quite a bit of similarity between struct declaration and struct instantation...

Currently it's struct A { x: uint } which I'd read as A is a struct where x is a uint, in instantation A { x: 3 } which means it's the struct A with x being 3.

With this proposal it'd read A { x = 3 } – what is that supposed to mean? x is assigned 3? No. In my opinion this becomes less intuitive, if not for struct initalizers, then for pattern matching.

match a {
    A { x = 3} => {}
}

In this example, no assignment is going on, still a = symbol is used.

(My thoughts on this.)

@nrc
Copy link
Member Author

nrc commented May 8, 2014

I guess people who like the status quo interpret : as meaning 'is', whereas I interpret : as meaning 'has type', so whilst = in a struct literal is not assignment, it seems to be closer to assignment then to typing. I guess == is what means 'is' and so should be used, but that doesn't make intuitive sense to me.

@nrc
Copy link
Member Author

nrc commented May 8, 2014

@kballard I think the fact that we are relying on finding : in a block to make it a struct literal is really nasty, and we should fix that, independent of the syntax for struct literals.

@nrc
Copy link
Member Author

nrc commented May 8, 2014

To make the general type ascription thing a little bit more concrete and less of a straw man, I lay out how I think it should work. Note that this isn't definitive in any way nor a proper proposal for it.

I think the idea is that anywhere an expression is accepted in rust code (expr) we would accept expr : type which has the same dynamic semantics as expr but statically checks that the type of expr has a subtype of type and assigns type as the type of expr: type. That is it acts like an explicit up cast. The same applies for patterns.

This makes using patterns a bit cleaner. Some formal syntax for let (match would work similarly):

st ::= 'let' pt '=' expr ';' | ...
pt ::= p | p ':' ty
p ::= var | '(' pt* ')' | name '{' (name ':' pt)* '}' | ...

Note that there is no special case in let for let x = ... vs let x: T = ....

Using this, the following example would be legal (but kind of pointless):

let (x, y, z: int) = ("bob", "alice": &'static str, 45);

The problem is with struct literals:

'S { x : t }'

Should this be parsed as a struct literal or a variable name (S) followed by a block which contains just the variable x and asserts it has type t.

@SiegeLord
Copy link

Is the implicit claim here that resolving @huonw's point about parsing initializers with = is easier than fixing the parsing issue with the type ascription? It seemed to me that your original reply implied that it's the same problem... which means that it's unfair to rail against the current struct initializer syntax when the = syntax is plagued by the very same problem.

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@nick29581 Patterns aren't expressions. So your assertion that let would change as a result of type ascription on expressions doesn't make sense. Defining the : syntax in patterns is orthogonal to defining the type ascription operator for expressions. And since we already have a way to define the type of the pattern as a whole, I don't think we need the ability to explicitly type individual sub-patterns.

As for parsing struct literals, the ambiguity you mention is the same one I mentioned earlier. But my expectation is that the chance of someone writing S { x : t } and intending for that to be a type-ascripted variable in a block is pretty slim. That's why I'm ok with explicitly resolving that ambiguity in favor of struct literals.

@SiegeLord @huonw's point was that parsing initializers with = is ambiguous today. If we add type ascription using :, then our existing struct literal syntax becomes ambiguous in the same fashion. However, I think the chances of someone writing S { x = y } and wanting assignment are significantly higher than someone writing S { x : t } and wanting type ascription.

Which is to say, I think changing struct literals to = is definitively ambiguous, with no clear resolution. Keeping struct literals as : is unambiguous. Adding type ascription introduces ambiguity, but with a much more obvious resolution.

@ftxqxd
Copy link
Contributor

ftxqxd commented May 8, 2014

👍 This makes the structure initialisation syntax more consistent with everything else IMO. = would be used for assigning values to things (in this cases the fields of structs) and : for type ascription and definition (in other words, : would always be followed by a type).

I don’t see any potential ambiguity arising from this — Struct { field = value } should be able to be parsed as a struct initialiser easily. (When would an identifier be followed by a block with no semicolon in between?)

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@nick29581 You seem to be just hand-waving when you say that the ambiguity with = can be resolved. What do you mean by "generalising struct initialiser syntax"? If you have some suggestion for resolving the ambiguity, please explain, because it's not obvious. And if you do have a solution for resolving the ambiguity, can it not work for : as well?

@P1start if Foo { x = 1 } { y = 2 }. How do you parse that? Is it equivalent to if (Foo { x = 1 }) { y = 2 }, where Foo { x = 1 } is a struct literal and { y = 2 } is the then-block, or is it if (Foo) { x = 1 } { y = 2 }, where Foo is an expression, { x = 1 } is the then-block, and { y = 2 } is a standalone block afterwards?

@ftxqxd
Copy link
Contributor

ftxqxd commented May 8, 2014

@kballard I hadn’t considered that. This is the only time I’ve actually wanted C-style brackets around the conditions in if statements :P (obviously that’s not an option). But because struct initialisers never return bool, having one on its own in an if condition (if (Foo { x = 1 }) { y = 2 }) is pointless, so it could be parsed as if (Foo) { x = 1 } { y = 2 }. However, this is indeed beginning to sound rather hacky and inconsistent (resolving syntactic ambiguities based on semantics? :/).

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@P1start Struct initializers never return bool, but the parser doesn't know that. And the same problem affects for x in Foo { x = 1 } { y = 2 }, where the struct could legitimately implement Iterator.

@ftxqxd
Copy link
Contributor

ftxqxd commented May 8, 2014

@kballard Yeah, it’s futile for me to continue to try to find a way for this to work. I’d like to know what @nick29581 has in mind, though. I just think that keeping struct initialisers with : is just delaying the problem for if/when we have type ascription expressions.

@nrc
Copy link
Member Author

nrc commented May 8, 2014

@kballard if I appear to be hand waving, it is because I am :-) Like I said, I don't want to spend time working it out if this is not going anywhere.

A simple solution would be that for and if expressions only take a subset of expressions before their blocks and that subset excludes struct literals (given that it doesn't make much sense to use a struct literal in either place, we don't lose anything, and if you really wanted to put one there you could use brackets). I expect you could do this to resolve any ambiguity with :.

I was thinking of something more complicated wrt generalising the struct literal which would mean defining a struct literal as just a name and a block and extracting the field values from the block, rather than making a struct literal its own syntactic construct, but I think that is neither necessary nor sufficient, actually.

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

Trying to define if and for expressions as taking some subset of expression that includes everything except struct literals sounds like a giant hack.

@huonw
Copy link
Member

huonw commented May 8, 2014

One option would be being greedy, so for a in x { y = ... } would attempt to parse x { y = ... } as a struct initialiser, requiring for a in (x) { y = ... } to parse as loop over x.

@nrc
Copy link
Member Author

nrc commented May 8, 2014

@kballard it is not as big a hack as looking into an expression to check for the presence of an : which we currently do. In formal terms it just adds an extra syntactic category to the grammar.

Really, the mistake is in the formulation of if and for expressions to take two expressions next to each other with no separating terminal, when expression boundaries can't be determined without context. Anything that gets around that is going to be a bit hackey. But reducing the set of expressions to a subset which allows context-free identification feels good (relatively) to me.

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

The mistake is not that if and for take two adjacent expressions (they actually don't, the second must be a block, but that's not really important for my point). The problem is that we have an expression type, struct literals, that could just as easily be parsed as two adjacent expressions. Our solution right now is a hacky lookahead to see if the second expression (the block) is actually legitimately an expression, or if the sequence identifier, ':' can be detected to signal that it's a struct literal.

Fundamentally, as long as our struct literals look like Name { ... }, we have a problem. But having struct literals look like that is so damn convenient!

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@huonw That's actually not a terrible idea. And it would work just as well with the current : syntax.

@bill-myers
Copy link

That could be fixable by using Name(...) instead of Name {...}.

One could also then possibly eliminate the field names and switch to the Scala model where all structs are initialized using Name(value1, value2) (omitting the field name), but accessed using field names; I'm not sure whether this is good or not.

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@bill-myers That rather strongly violates the principle that type initialization should look like type declaration. Name(...) to initialize a type right now strictly refers to types defined as struct Name(...), and using it for types defined as struct Name { ... } feels quite wrong.

@nikomatsakis
Copy link
Contributor

On Wed, May 07, 2014 at 10:18:27PM -0700, Kevin Ballard wrote:

@P1start if Foo { x = 1 } { y = 2 }. How do you parse that? Is it
equivalent to if (Foo { x = 1 }) { y = 2 }, where Foo { x = 1 }
is a struct literal and { y = 2 } is the then-block, or is it if (Foo) { x = 1 } { y = 2 }, where Foo is an expression, { x = 1 } is the then-block, and { y = 2 } is a standalone block
afterwards?

The answer is that we would define a subset of expressions that can
appear in if conditions, match conditions, and so forth, and that
subset would exclude struct literals. I rather feel this is what we
should do anyway.

@nikomatsakis
Copy link
Contributor

On Wed, May 07, 2014 at 11:23:01PM -0700, Huon Wilson wrote:

One option would be being greedy, so for a in x { y = ... } would
attempt to parse x { y = ... } as a struct initialiser, requiring
for a in (x) { y = ... } to parse as loop over x.

I'd be inclined to bias the other way. Which is more common: iterating
over x, or iterating over a struct literal?

@lilyball
Copy link
Contributor

lilyball commented May 8, 2014

@nikomatsakis With the proposed = syntax I agree, but with the current : syntax (that I'm strongly in favor of keeping), it's rather unlikely that someone will write x: t as the first expression in a for or if block (it makes no sense at all for the for block, and it makes only very little sense for the if block (and has the trivial workaround of (x: t)).

That said, defining a subset of expressions that excludes struct literals is a reasonable course of action. I know I said it was a hack earlier, but I'm coming around. Of course, if we do that, then I think we also need to expose this new expression type to macro_rules! macros.

@nrc
Copy link
Member Author

nrc commented May 11, 2014

I'm sympathetic to some of the anti-= arguments here - in particular that the ordering becomes weird in patterns and that in both cases it is not quite assignment. However, I still really dislike : and prefer =. I'd be totally happy to entertain other symbols (which would mean losing the declaration/initialisation symmetry, but I think that is no big deal, really).

Any new symbol must not be directional since that would have the same problems of odd directionality in patterns (which means := and <- don't work). Some ideas would be ==, since we are making the sides equal, this is kind of similar to testing they are equal, and @ which we already use for patterns to bind a pattern to a variable, I think that works well for patterns, but seems a bit odd for struct literals. I still prefer =, but does anyone prefer either of those, or have any better ideas?

@lilyball
Copy link
Contributor

@nick29581 Why do you really dislike :? I haven't seen a specific explanation, but I can only assume it's because you view : as needing a type on the right-hand side. Is this correct?

My view is that right now we have the two forms slot: type (used for static, let-bindings, and struct definitions), and field: expr, which is used in struct literals. But that's ok. The struct literal is a value, where field corresponds to the same-named slot in the definition, and expr is a value with the same type type in the definition. Basically, slot is a component of the overall struct type, and this component has the type type. field is a component of the overall struct value, and this component has the value expr. The correlation is the same. slot is part of a type, and it has its own type. field is part of a value, and it has its own value.

This also preserves the semantic meaning of : as "is". In a struct,

struct Foo {
    a: int
}

declares a type wherein the slot a is int. In a struct literal, Foo { a: 3 } declares a value wherein the field a is 3. The former is a declaration of a type, and so the slots themselves declare types. The latter is a declaration of a value, and so the fields themselves declare values.


Type ascription using : is the only thing that throws a wrinkle in this. If you consider it as given that type ascription will use : as the operator, then it suggests that the meaning of : is "has type" rather than "is". This may be an argument to use a different operator (although the question of what operator is a hard one to answer). But I don't think that's necessary. I think we can continue to think of : as "is" and just accept that type ascription is using "is" to relate a value and a type. Reading 3 : int as "3 is int" is not that much of a stretch.

@dobkeratops
Copy link

@nick29581 One of my suggestions was to change mutation to := <- or even "mut lhs=rhs" .. to free up "=" to always mean binding/initialization (let, struct initialisers, patterns and maybe eventually keyword args :) )
:= mutation would be the opposite of Go, but i don't think thats' a serious problem.
I like the idea of : always introducing a type. Very easy to syntax highlight.

@nrc
Copy link
Member Author

nrc commented May 12, 2014

@dobkeratops oh, I'm sorry, I misread. I would be very much in favour of that - I prefer := for assignment/mutation and freeing up = for struct literals (and maybe to replace @ in patterns too) would be great. Unfortunately, I think it is not a popular view - it doesn't fit with the C++ heritage and is not so popular in general, I think. I guess it would look a bit odd when you gave a type in a let expression (let x: int := 5)

@nrc
Copy link
Member Author

nrc commented May 12, 2014

@kballard pretty much. I dislike : because I think it should be an operation on types - I don't mind using it for inheritance and bounds because they are in the domain of type-like things. But I think it is a nice syntactic divide to restrict it to typing. In particular, in the context of blocks/statements/expressions it should only be used for type assignment/ascription. Basically, I never read : as 'is', I read it as has type or has bound (where type and bound are from the sort of the same syntactic categories).

Thinking of : as assigning something to the slot on the lhs doesn't hold for bounds/inheritance. I believe thinking of variables is instructive - in let x: T = v, we use : for giving the variable a type and = for giving the variable a value. We don't write let x: v, or let x: T : v. Since fields are just variables with an indirection, I want the use of variables and fields to be as symmetric as possible.

Getting a bit more fundamental, or hand-wavey, depending on your point of view, I think the language of expressions (which includes values and variables) and the language of types in a programming language are distinct and should use different syntax. My unease around struct literals and the dissonance we would have with type ascription are symptoms of violating that principle.

@tbu-
Copy link
Contributor

tbu- commented May 12, 2014

@nick29581 Depending on your point of view, the current format is about having nearly the same syntax for structs and struct instantiation, which would always be violated if you choose to use different symbols for is of type and is of value.

As you can see I parse the : as is. It isn't really an assignment because you're referencing an instantiation of the struct.

let x = SomeStruct { x: 4 };
      ^ assignment happens here
let x = SomeStruct { x: 4 };
                      ^ this is just describing one possible instance of SomeStruct

So I think it's reasonable to have different symbols for these two.

@bstrie
Copy link
Contributor

bstrie commented May 12, 2014

-1 to changing assignment syntax solely to solve this papercut, and -1 to multi-token symbols to replace : in struct initialization.

@bstrie
Copy link
Contributor

bstrie commented May 12, 2014

That said, if we ever happened to ditch immutability-by-default for whatever reason, requiring := for mutating assignment might be enough to mollify the mutation-wary. But that's getting ahead of ourselves.

@dobkeratops
Copy link

@nick29581 >>" I guess it would look a bit odd when you gave a type in a let expression (let x: int := 5)"

.. the idea was that would still just be 'let x:int =5' - because its an initialization, not a mutation. use the context of let .
I realise sometimes the 'initialization' is defered though, compile time checks avoid.. I still like the idea of declaring initialized where possible

@brson
Copy link
Contributor

brson commented May 21, 2014

Closing. It's very late to be changing this and the benefits aren't overwhelming.

@brson brson closed this May 21, 2014
withoutboats pushed a commit to withoutboats/rfcs that referenced this pull request Jan 15, 2017
Fix an off-by-one in the count to send shutdown messages.

Closes rust-lang#65
wycats pushed a commit to wycats/rust-rfcs that referenced this pull request Mar 5, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.