(Opinion) Declarations in Rust are inelegant


#1

Hello,

I’ve only recently discovered Rust. I love the language, and I’ve successfully converted some of my projects to it. Despite all that, I have the feeling that the language lacks one thing: elegance. And after pondering why, I’ve come to the conclusion that it’s because of the way functions and variables are declared.

Before I develop, I will admit how little weight my opinion has, because:

  • I am nobody
  • Elegance is subjective
  • Rust creators/maintainers have advanced computer degrees, which I don’t
  • Now that version 1.0 has come out, there is no incentive to change the declaration syntax and break compatibility

But here I go. To declare functions and variables in Rust, you do:

let variable: int = 2;
fn function(x: int, y: int) -> int { ... }

First of all, the required “let” and “fn” keywords seem completely unnecessary to me. The Rust compiler should be able to guess on its own whether I am declaring a function or a variable. Second of all, the “->” syntax is not used anywhere else in Rust. I see no point in it. Common intuition would want to use “:” as everywhere else.

So, in my ideal world, declarations in Rust would look like this:

variable: int = 2;
function(x: int, y: int): int { ... }

This would allow Rust to be as concise as C/C++:

int variable = 2;
int function(int x, int y) { ... }

or even more concise, because Rust has type inference.


#2

Elegance is indeed subjective, but Rust syntax is actually really nice when you parse it! (Well, unless you are looking into some really dark corners :slight_smile: ).

Keywordles C++ style may be more concise sometimes, but don’t forget about the most vexing parse!


#3

Nit: int is not a type in Rust.

Ahh, but it’s not always about guessing. For example, if I’m trying to find where a function is declared, I can grep "fn foo" and find the declaration, every time. And for let, well, let introduces a pattern, whereas the = version does not. variable = 5; is a valid Rust statement, but it works differently than introducing new bindings, which have a lot more power than simple assignment.

This was discussed, but it was decided that we preferred ->. It sticks out a bit more than : in this kind of context, and is also less ambiguous in others:

// actual rust
fn foo(f: fn(i32) -> i32, i: i32) {

// with proposed syntax
fn foo(f: fn(i32): i32, i: i32) {

interspersing the :s here makes it much harder to read, imho.

Right, but we also chose to not do global inference as well, because of action-at-a-distance issues.

I’m glad you’re otherwise enjoying Rust!


#4

Username is literally ‘nobody’

:joy:

But seriously, it’s good to question even the most basic things. I’m not sure how your proposed syntax would work with some basic cases, though.

For example:

x: i32 = 2; // declare variable x

y = 4;      // does this declare a new variable y whose type is inferred to be i32,
            // or does this overwrite the value of an existing y?

How would you suggest to signal mutability? To show a more complicated example: I’m a big fan of shadowing, i.e.

let mut x = Stuff::new(42); // x is mutable
x.do_magic_on_self();       // call method(s) that mutates x

let x = x.finish();         // Okay, we have built our x, no need to mutate it any further,
                            // so rebind x to be non-mutable, but with the same name!

#5

In my ideal world functions look like functions in math, e.g. f(x,y) -> z. Your example look hard to parse IMO. Is it a function? Some kind of struct definition (yeah I get it is called function in real world it would be flub(x:int, y: int): int.

Also Rust tries to infer parameters values so variable : int = 2 is written as variable = 2 which is IMO better.


#6

Thank you all for responding seriously to my post! In light of what you’ve said, the “let” and “fn” keywords could be made optional, instead of required. So, the compiler could accept as valid declarations:

let variable: i32 = 2;

and:

variable: i32 = 2;

and:

variable = 2; // first occurrence

This would:

  • Preserve compatibility with existing code
  • Avoid confusion in some extreme cases, such as the ones matklad and killercup described
  • Allow methodical people, such as steveklabnik, to make their code completely explicit, if they choose to do so
  • Allow non-methodical people, such as me, to let the compiler do all the unnecessary explicitations

I still maintain that “->” is not intuitional, because it’s not used anywhere else in the Rust language. A function declaration should have some sort of similarity with a variable declaration, even if, according to steveklabnik and Ygg01, this would make it harder to parse.

Anyway, I realize how subjective this is, so I won’t insist and won’t post any further. I just wanted to voice my opinion, in case other people happen to share it and stumble on this thread one day.


#7

Personally I don’t mind fn. I’m used to writing the whole function() in JS, so Rust is an improvement for me :wink:

However, on topic of inelegance, I find declarations with generics — with tons of position-dependent and nested angled brackets — hard to read, and hard to mentally parse.