Replace Point { x: 3, y: 5 } with Point { x=3, y=5 }

Would you mind replying with “+1” if you prefer = or with “-1” if you prefer the current :?

Here are my reasons for me liking =, as copied from my closed RFC:

A = between the field name and the value in struct expressions just seems a much better fit than :, given the context of the Rust language and other languages with similar syntax. Before RFC 25, using = instead of : wouldn’t have worked, because = would have created an ambiguity between a struct expression and a block with an assignment, but now the token can be chosen to be whatever seems fit.

In other languages with a similar construct, = is more widely used.

In OCaml:

type point = {x: int; y: int};;
{ x=3; b=5 };;

In Haskell:

data Point = Pt {x, y :: Float}
Pt {x=3, y=5}

In Python:

Point = namedtuple('Point', 'x y')
Point(x=3, y=5)

In Rust itself, beside struct expressions, the pattern A: B is used for declaring types and for declaring type boundaries. In both of these cases, A: B can be read as "A is a B", or "A is a member of the set B". That is, the more general entity is on the right. This certainly can’t be said of x: 3.

On the other hand, = is used mainly for assignments. In let p = Point { x=3, y=5 } it can certainly be said that p.x is assigned the value 3. Another usage of = is in keyword arguments (Currently in the fmt! family of macros, perhaps some day in general functions). The use of = for keyword arguments also aligns well with using = for struct expressions, since the form Point { x=3, y=5 } can be seen as a special constructor which accepts a keyword argument for each field.

There are more discussions about this at the closed RFC PR 65.

14 Likes

0 I don’t mind it either way, but I’m interested in (a) keyword arguments and (b) anonymous structs

so along similar lines, i would suggest considering := somewhere to disambiguate things…

option [1] := for mutating assignment, and ‘=’ is always related to creation & initialisation (e.g. let, structs, keyword args)

option [2] the opposite. := for struct initialisers and keyword args

… but i suppose => could also be used for keyword args, and you could use ‘_’ for inferring struct name ? return _{ a:… , b:… }

I think that since : is used so extensively, I would have used <- for assignments. But this is surely not going to happen.

Ok. If you prefer to replace : with =, click the heart below this message. If you prefer to keep :, click the heart below the message below.

32 Likes

If you prefer to keep :, click the heart below this message. If you prefer to replace : with =, click the heart below the message above.

16 Likes

+1

“:” seem less consistent with the language to me.

2 Likes

+1 I made a comment on the reddit thread detailing my reasons why I think this is a good idea:

Think of it this way: in the declaration of a binding/variable, : is used to declare the type of the binding. In the declaration of a struct, : is used to declare the types of each field. In the initialisation of a binding/variable, = is used to declare the value of the binding. In the initialisation of a struct, : is used to declare the value of each field. Wait, what? Continuing to use : to construct structs would be like changing this:

let x: int;
x = 1;

to

let x: int;
x: 1;

With the proposed change, both : and = would be consistent—: is always followed by a type, and means that the preceding name is of that type. = is always assigning a value to something. Perhaps the struct syntax could even be extended to allow type ascriptions while constructing a struct:

let x = Point {
    x: f64 = 2.5,
     y: f64 = 6.2,
}

This could be useful with generic structs, perhaps, or even just for clarity. And then you still have that similarity between the struct declaration and the struct initialisation, but it’s clearer here—the colon means the same thing.

2 Likes

Reposted from Reddit:

I proposed this the last time around, and I still prefer =. But I’m not willing to push on this again.

Anyway, the killer argument in support of the status quo (I think) is that initialisation and destructuring must match and if we use = in destructuring then the assignment is going the wrong way round, e.g.,

let Point { x = a, y = b} = p;

'Assign’s into a and b where a reader might expect that we assign into x and y.

I see no way to address this issue in a nice way.

My suggestion is to prefer using @ in this situation:

let Point {a @ x, b @ y} = p;

Which is currently legal syntax and would presumably remain so if we moved from : to =. However, @ bindings are not widely used and this also binds x and y as local variables to p.x and p.y which is kind of surprising. And it doesn’t really solve the issue of x = a assigning into a. So all in all, not a great solution.

2 Likes

And yet another repost from reddit (what should we do to stop these?):

Yes, it’s strange.

But I think there’s some sort of strange reversal in all destructures. In the case of enum variants, Point(x, y) usually takes the existing x and y and produces a point, while let Point(x, y) = p takes a point and produces x and y. &x is usually used to create a reference from x, but let &x = r creates a value from a reference.

I’m just saying that coming from Python, all of these need a moment of thought before I realize their meaning. And it’s not that let Point { x: a, y: b} is much clearer, although it doesn’t suggest that a is assigned to x so strongly.

Perhaps the solution would be to allow to omit the names, and have

let Point {a, b} = p;

or just recommend the boring, tiresome, but not confusing:

let a = p.x;
let b = p.y;
2 Likes

-1

= violates initialization-follows-declaration. I also believe that : is more common than = in other popular languages. C is the obvious exception, but { .name = value } field initialization is not the common case in C struct literals.

+1 I’d definitely prefer some consistency between declaring the type of a variable and assigning a value.

Regarding other popular languages: as I wrote in the RFC, OCaml, Haskell and Python, which have a strong influence on Rust, use =. Javascript, Ruby, Go and (in a somewhat different context) Python use :, but this feels fine in these languages since they don’t have A: B meaning anything else, whereas in Rust A: B is used extensively for something completely different.

I don’t think there’s actually a rule “initialization follows declaration”. You declare

let x: int;

and initialize

x = 3;

You also declare

enum E { A(int), B(f64) }

and initialize

A(3)

-1

Because ‘:’ requires less work to type than ‘=’, and doesn’t move the right hand out of reach of the b in C-b.

I don't think the {k : v} syntax is hampered by the additional meaning of : T in the type system. Instead, its a very common pattern for a key-value store, and that's what I recognize when I see it.

1 Like

@nrc

After reading the discussion on the original RFC and here, I wonder: Would a combination of C99 initializer syntax and inverting the order in patterns be a solution here?

E.g.:

struct Foo { a: int, b: int }

let p = Foo { .a = x, .b = y }; // initializer
let Foo { j = .b,  i = .a } = p; // pattern, no visual ambiguities

Though to be honest, I am okay with colon; I just thought this was an interesting combination of ideas.

13 Likes

+1

I think ‘=’ looks better and is clearer than the current use of ‘:’. I also don’t think destructuring poses a problem. I feel that after you’ve wrapped your head around how destructuring works, it makes just as much sense as anything else (e.g., let &y = x dereferencing x).

It's not a hard rule, but it's nevertheless a rule that Rust tries to follow when possible.

That's not a type declaration. The rule applies to types, not values.

Yes, A(int) is the declaration, A(3) is the value constructor. Same syntax. Similarly for structs, struct Foo { x: int } is declaration, and Foo { x: 3 } is value construction. And for tuple-structs, struct Foo(int); is the declaration and Foo(3) is the value construction.

3 Likes

+1

‘=’ seems much more consistent, and ‘:’ already serves double duty for records and namespaces.

I see what you mean, but if you’ll allow me to be pedantic, the type of A(3) is E, and it’s declaration is enum E { A(int), B(f64) }, not A(int).

But the truth is that I don’t see following this rule a reason to have a confusing syntax.

-1, i like Point { x: 3, y: 5 },just like json in javascript

1 Like