Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I'm not sure which of the dozen Rust-syntax supporters I should reply to, but consider something like these three (probably equivalent) syntaxes:

    let mut a = Vec::<u32>::new();
    let mut b = <Vec::<u32>>::new();
    let mut c = <Vec<u32>>::new();
    let mut d: Vec<u32> = Vec::new();
Which one will your coworker choose? What will your other corworkers choose?

This is day one stuff for declaring a dynamic array. What you really want is something like:

    let mut z = Vec<u32>::new();
However, the grammar is problematic here because of using less-than and greater-than as brackets in a type "context". You can explain that as either not learning from C++'s mistakes or trying to appeal to a C++ audience I guess.

Yes, I know there is a `vec!` macro. Will you require your coworkers to declare a similar macro when they start to implement their own generic types?

There are lots of other examples when you get to what traits are required to satisfy generics ("where clauses" vs "bounds"), or the lifetime signature stuff and so on...

You can argue that strong typing has some intrinsic complexity, but it's tougher to defend the multiple ways to do things, and that WAS one of Perl's mantras.





This is like complaining that in C you can write

    a->b
    (a->b)
    (*a).b
    ((*a).b)
Being able to use disambiguated syntaxes, and being able to add extra brackets, isn't an issue.

PS. The formatting tooling normalizes your second and third example to the same syntax. Personally I think it ought to normalize both of them to the first syntax as well, but it's not particularly surprising that it doesn't because they aren't things anyone ever writes.


> This is like complaining that in C [...]

It's really not. Only one of my examples has the equivalent of superfluous parens, and none are dereferencing anything. And I'm not defending C or C++ anyways.

When I was trying to learn Rust (the second time), I wanted to know how to make my own types. As such, the macro `vec!` mentioned elsewhere isn't really relevant. I was using `Vec` to figure things out so I could make a `FingerTree`:

    let v: Vec<u32> = Vec::new();  // Awfully Java-like in repeating myself

    let v = Vec::new(); // Crap, I want to specify the type of Vec

    let v = Vec<u32>::new();  // Crap, that doesn't compile.
And so on...

> let v = Vec::new(); // Crap, I want to specify the type of Vec

This kinda implies you've gone wrong somewhere. That doesn't mean there aren't cases where you need type annotations (they certainly exist!) but that if `Vec::new()` doesn't compile because the compiler couldn't deduce the type, it implies something is off with your code.

It's impossible to tell you exactly what the problem was, just that `<Vec<T>>::new()` is not code that you would ever see in a Rust codebase.


Nah, there's lots of times you need to specify the types of Vec, either because

1. You don't want the default `i32` integer type and this is just a temporary vector of integers.

2. Rust's type inference is not perfect and sometimes the compiler will object even though there's only one type that could possibly work.

Edit: The <Vec<T>>::new() syntax is definitely never used though.


Or just collect::<Vec<_>>() when you have up doing everything in a lazy pattern and want a concrete type again.

Which I guess i typical stumbling block when the compiler can’t infer what type to collect into.


I've only ever seen `a` and `d`. Personally I prefer `a`. The only time I've seen `c` is for trait methods like `<Self as Trait<Generic>>::func`. Noisy? I guess. Not sure how else this could really be written.

Fwiw, I didn't go looking for obscure examples to make HN posts. I've had three rounds of sincerely trying to really learn and understand Rust. The first was back when pointer types had sigils, but this exact declaration was my first stumbling block on my second time around.

The first version I got working was `d`, and my first thought was, "you're kidding me - the right hand side is inferring it's type from the left?!?" I didn't learn about "turbo fish" until some time later.


> The first version I got working was `d`, and my first thought was, "you're kidding me - the right hand side is inferring it's type from the left?!?" I didn't learn about "turbo fish" until some time later.

Tbh d strikes me as the most normal - right hand sides inferring the type from the left exists in basically every typed language. Consider for instance the C code

    some_struct a = { .flag = true, .value = 123, .stuff = 0.456 };
Doing this inference at a distance is more of a feature of the sml languages (though I think it now exists even in C with `auto`) - but just going from left to right is... normal.

I see your point, and it's a nice example, but not completely parallel to the Rust/StandardML thing. Here, your RHS is an initializer, not a value.

    // I don't think this flies in C or C++,
    // even with "designated initializers":
    f({ .flag = true, .value = 123, .stuff=0.456});

    // Both of these "probably" do work:
    f((some_struct){ .flag = true, ... });
    f(some_struct{ .flag = true, ... });

    // So this should work too:
    auto a = (some_struct){ .flag = true, ... };
Take all that with a grain of salt. I didn't try to compile any of it for this reply.

Anyways, I only touched SML briefly 30 some years ago, and my reaction to this level of type inference sophistication in Rust went through phases of initial astonishment, quickly embracing it, and eventually being annoyed at it. Just like data flows from expressions calculating values, I like it when the type inference flows in similarly obvious ways.


Most likely

    let e = Vec::new()
or

    let f = vec![]
rustc will figure out the type

exactly. you specify types for function parameters and structs and let the language do it's thing. it's a bit of a niche to specify a type within a function...

There is a reason the multiple methods detailed above exist. Mostly for random iterator syntax. Such as summing an array or calling collect on an iterator. Most Rust devs probably don't use all of these syntax in a single year or maybe even their careers.


I can't believe that a flexible powerful syntax is considered limiting or confusing by some people. There is way more confusing edge-case syntax keywords in C++ that are huge foot-guns.

Do these print statements print the same thing?

    let i = 1;
    let j = 1;
    print!("i: {:?}\n", !i);
    print!("j: {:?}\n", !j);

    let v = vec![1, 2, 3];
    v[i];
There are definitely times you want to specify a type.

> Which one will your coworker choose? What will your other corworkers choose?

I don’t think I’ve ever seen the second two syntaxes anywhere.

I really don’t think this is a problem.


This will be the case in any language with both generics and type inference. It's nothing to do specifically with Rust.

I mean, the fact that you mention "probably equivalent" is part of the reality here: Nobody writes the majority of these forms in real code. They are equivalent, by the way.

In real code, the only form I've ever seen out of these in the wild is your d form.


Agree. This isn't really a problem unless you also think that extra parentheses is a problem.

In many languages you could write:

> if (a + b) > (c + d)

or

> if a + b > c + d

And they're equivalent. Yet nobody complains that there are too many options.


This is some True Scotsman style counter argument, and it's hard for me to make a polite reply to it.

There are people who program with a "fake it till you make it" approach, cutting and pasting from Stack Overflow, and hoping the compiler errors are enough to fix their mess. Historically, these are the ones your pages/books cater to, and the ones who think the borrow checker is the hard part. It doesn't surprise me that you only see code from that kind of beginner and experts on some rust-dev forum and nothing in between.


The issue though is that this isn't a solvable "problem". This is how programming languages' syntax work. It's like saying that C's if syntax is bad because these are equivalent:

  if (x > y) {

  if ((x > y)) {

  if (((x) > (y))) {
Yes, one of your co-workers may write the third form. But it's just not possible for a programming language to stop this from existing, or at least, maybe you could do it, but it would add a ton of complexity for something that in practice isn't a problem.

Well, the solution usually isn't in syntax, but it often is solved by way of code formatters, which can normalize the syntax to a preferred form among several equivalent options.

I certainly would support rustfmt turning those redundant forms into the simpler one.

I suspect rustfmt would consider this out of scope, but there should be a more... "adventurous" code formatter that does more opinionated changes. On the other hand, you could write a clippy lint today and rely on rustfix instead

Only `b` has the equivalent of "superfluous parens".

It's practically your job to defend Rust, so I don't expect you to budge even one inch. However, I hate the idea of letting you mislead the casual reader that this is somehow equivalent and "just how languages work".

The grammar could've used `Generic[Specific]` with square brackets and avoided the need for the turbo fish.


It hasn't been my job to work on Rust in for years now. And even then, it was not to "defend" Rust, but to write docs. I talk about it on my own time, and I have often advocated for change in Rust based on my conversations with users.

If you're being overly literal, yes, the <>s are needed here for this exact syntax. My point was not about this specific example, it's that these forms are equivalent, but some of them are syntactically simpler than others. The existence of redundant forms does not make the syntax illegitimate, or overly complex.

For this specific issue, if square brackets were used for generics, then something else would have to change for array indexing, and folks would be complaining that Rust doesn't do what every other language does here, which is its own problem.


> For this specific issue, if square brackets were used for generics, then something else would have to change for array indexing

The compiler knows when the `A` in `A[B]` is a type vs a variable.


A compiler could disambiguate, but the goal is to have parsing happen without knowing if A is a type or a variable. That is the inappropriate intertwining of parsing and semantics that languages are interested in getting away from, not continuing with.

Anyway, just to be clear: not liking the turbofish is fine, it's a subjective preference. But it's not an objective win, that's all I'm saying. And it's only one small corner of Rust's syntax, so I don't think that removing it would really alleviate the sorts of broad objections that the original parent was talking about.


> The grammar could've used `Generic[Specific]` with square brackets and avoided the need for the turbo fish.

But then people would grouse about it using left-bracket and right-bracket as brackets in a type "context".


The problem here is that angle brackets are semantics dependent syntax. Whether they are brackets or not depends on semantic context. Conversely square brackets are always brackets.

Square brackets would be semantically dependent if they appeared in the same position of angle brackets. There's nothing magical about [] that makes the problems with <> disappear.

It disappears the problem that angle brackets are sometimes not brackets. I.e. a<b>c is parsed as (a<b)>c or as (a(<b>))c.

It also comes up when you want compile time expressions as parameters to your generics:

    // nice and clean
    let a = Generic[T, A > B]::new(); 

    // gross curlies needed because of poor choices
    let a = Generic::<T, {A > B}>::new();

So that’s the Specificth element of Generic?

It's Brackets(Generic,Specific).

Lol, yes they would. However, I wouldn't. :-)



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: