Hacker News new | ask | show | jobs
by foldr 1203 days ago
One random thing that bugs me about Rust's syntax is array initialization. In Go I can initialize an array (or strictly speaking a slice) of structs like this:

    var foo []my_struct = {{"foo", 1, "bar"}, {"foo", 1, "bar}, ...}
This is often useful in tests (where each struct value represents a test case). Rust doesn't seem to offer any similarly compact initialization syntax for arrays or Vecs. You have to write some abomination like this:

    let foo = [MyStruct{a: "foo", b: 1, c: "amp"}, MyStruct{a: "bar", b: 1, c: "fff"}, MyStruct{a: "amp", b: 1, c: "aaa"} ];
Sure, it's more explicit. But even if I add a type annotation to 'foo' specifying the array type, I still have to repeat MyStruct for every member.
4 comments

If you want compact, use an array of tuples:

  const foos = [("foo", 1, "bar"), ("foo", 1, "bar"), ...];
  for (str1, num, str2) in &foos {
      // ...
  }
For a proper struct you have to name the fields, because otherwise refactoring the fields could cause struct instances to silently get out of sync with the definition.
>otherwise refactoring the fields could cause struct instances to silently get out of sync with the definition.

Having to name the fields is only part of the pain. You also have to redundantly repeat the struct name. I don't see any fundamental reason why something like this shouldn't be valid:

    let foo: [MyStruct; _] = [{a: "foo", b: 1, c: "amp"}, {a: "bar", b: 1, c: "fff"}, {a: "amp", b: 1, c: "aaa"} ];
I can see the logic for insisting on field names. However, the builtin 'go vet' tool has a nice behavior where it will flag the use of unkeyed literals for public structs only. This strikes me as a good compromise between concision and safety.
You can use map() to turn an array of tuples into an array of structures. Unfortunately at time of writing the optimiser doesn't do a great job on this, so if you're making an array of several thousand of something, or an array of things which are themselves very large, this might have unacceptable performance, but in cases where I have say a modest N values and I want N structures based on those values...

  fn make_struct(x: (&str, u8, &str)) -> MyStruct {
     MyStruct { a: x.0, b: x.1, c: x.2 }
  }

  let foo = [("foo", 1, "amp"), ("bar", 1, "fff"), ("amp", 1, "aaa")]
    .map(make_struct);
[I have not actually compiled this code, but it should do roughly what you meant]
Dropping field names from definitions like Go would make the syntax inconsistent with destructuring and pattern matching. It's always possible to define a constructor function that takes unnamed values if you care about code verbosity in test cases. Or use `type M = MyStruct` to have an abbreviated type name within the test function.

Rust is more on the verbose/explicit side and I agree that can sometimes be annoying, but as autocompletion exists I can live with it.

>Dropping field names from definitions like Go would make the syntax inconsistent with destructuring and pattern matching

There's no reason you couldn't match on struct fields positionally as well. It would actually be quite convenient for cases like struct Point { x: f64, y: f64 }.

It might be convenient at first but could become a frustrating bug if you change the struct fields and also less readable in some cases. It has its upsides but I personally think making the syntax more complex for this one detail isn't worth it.

Side note: matching f64 by value isn't a good idea either way, is deprecated and will become an error in the future.

>matching f64 by value

I wasn't thinking about matching floats by value, just destructuring a Point to obtain the coordinates as separate variables. Come to think of it though, as Rust already allows

    let Point{x, y} = pl
there would be little benefit to allowing positional matching (and in fact it would create a nasty ambiguity). So yeah, as you said, you'd have to reserve the positional syntax for initialisation.

My overall point here isn't that Rust has made bad design decisions. It's just that the end result of a bunch of sensible design decisions is that initialising arrays of structs is clunky. It might well be that this problem isn't fixable without creating other, worse, problems. However, I think it's a problem that's emblematic of what the OP was talking about. We have here a language that's so concerned with solving difficult problems in clever ways that it's ended up backed into a corner when it comes to something as ridiculously simple as initialising an array of structs.

> It's just that the end result of a bunch of sensible design decisions is that initialising arrays of structs is clunky.

Yes, though at least the language gives you tools to make it more convenient. In the previous examples that would be constructors, and macros can help too (`println`, `vec` etc. exist for that reason).

I myself like to create tiny macros to shorten things like `UnicodeSegmentation::graphemes(s, true).count()` when I have to properly handle unicode. Sure I would prefer to have it be shorter from the start, though that wouldn't make it as obvious how expensive the operation is compared to just getting the size in bytes.

you'll have to forgive me, but isn't this why there should be a `::new()` callable attached to the struct?

I'm really new at Rust, but that was my takeaway, e.g. `String` has `String::new()`

The convention of providing a new() function isn't relevant here, that name isn't magic, the Rust compiler doesn't care whether it exists, and it won't cause Rust to do anything special with tuples (or any other data structure) that happen to have a similar shape.

String::new() just makes you an empty String, which, since an empty String doesn't own any storage and doesn't contain anything, is very cheap (and indeed constant evaluable), likewise Vec::new() makes an empty Vec.

What your parent commenter wants is for Rust to make the appropriate MyStruct, but without them needing to say MyStruct each time, which would have worked in e.g. C or Go.

Do you mean the vec! macro?
It doesn't let you drop redundant struct tags, as far as I can tell.
I think you're right, although mentioning a macro does suggest another option here, you could write a macro which transforms [{foo1, "bar1", baz1}, {foo2, "bar2", baz2}] into the named structure version.

Since it's a macro there's no impact on runtime performance, but on the other hand it's more work to debug it. Learning declarative macros in Rust is much nicer and safer than learning C macros, but nowhere near as powerful as Rust's horribly unsafe proc macros.