What is the actual reason of having `->` before return type

why -> is used?

instead of just the return type like Go

(Note this is purely personal speculation)

I could see several reasons to do so:

  1. It is more "mathematical" since that is generally the style for functions f(R) -> R
  2. Go require the wrapping of multiple return types in () but not single types. Rust doesn't.

Using -> notation for function types is common in functional languages like Haskell and the ML family, including OCaml. (Rust was originally written in OCaml.) It's also found in languages like Swift and Erlang, which have some common influences with Rust.

10 Likes

I’d like to remark that the similarities to mathematical notation or Haskell are a bit more limited than the previous comments might suggest. In maths or Haskell you give function signatures independent from the defining equation.

mathematical:

𝑓: β„€ β†’ β„€
𝑓(π‘₯) = π‘₯Β² βˆ’ 1

Haskell:

f :: Integer -> Integer
f x = x^2 - 1

Rust (although i64 is not quite all integers):

fn f(x: i64) -> i64 { x.pow(2) - 1 }

for the record, there’s also (among even more other alternatives):
mathematical:

𝑓: β„€ β†’ β„€
π‘₯ ↦ π‘₯Β² βˆ’ 1

3 Likes

What exactly are you referring to?

Rust doesn't have multiple return types. It has tuples though, and tuples do require ().

Also - how is this relevant to the ->?

1 Like

The -> is consistent with the Fn traits:

fn foo(f: impl Fn(i32) -> i32) -> i32 {
    todo!()
}

Whereas in Kotlin, functions use : but closures use ->, which I find inconsistent:

fun foo(f: (Int) -> Int): Int =
    TODO()

The same applies to TypeScript. In Go it is consistent:

func foo(f func(int) int) int {...}

But I find this less readable.

9 Likes

but i found this more clean

The usage of -> for function return types has been around since at least Rust 0.1 in 2012. For an example, see this function in libcore from release-0.1:

#[doc(
  brief = "Negation/Inverse"
)]
pure fn not(v: t) -> t { !v }

So I suspect that it's a combination of A) graydon's personal style choice, B) it not being a bad style, and C) momentum from people using Rust and becoming used to it.

Even today, it's fairly ambiguous whether it's good or not. @Aloso, you see it as positive. @milrope, you see it as negative. I'd argue that it's a fairly inconsequential style choice.

There are other similar marks that graydon has had on the project, even though Rust 1.0 was a very different language from when he had stopped working on it. For instance, the fn keyword also remains from that time, as do things with a larger impact, like implicit returns.

5 Likes

I have a really hard time reading these Go types. To me they look like a soup of keywords without any relation (in particular without syntax highlighting). Only parentheses and letters, this could almost be LISP. :wink: There's a reason many natural languages have particles that go between the nouns; -> plays the role of such a particle..

Of course, this is subjective.

26 Likes

I think there is no good reason for it. (The syntactic connection between functions and lambdas is overrated and never works out in practice.)

The biggest problem I have with it is that the visual rhythm while reading makes it look like it is part of the body, not part of the signature.

I stopped counting the time I have wasted trying to figure out why

fn foo(x: Something, y: SomethingElse) -> Foo { x, y }

didn't compile. Also, this Foo { Foo { stutter is really ugly to read in my opinion:

fn foo(x: Something, y: SomethingElse) -> Foo { Foo { x, y } }

So, yeah – using : like modern languages would have been way nicer and less confusing, and keeps the function's signature apart from the function's body:

fn foo(x: Something, y: SomethingElse): Foo = Foo { x, y }

If I find some time, I'll maybe hack IntelliJ to collapse -> to : visually because for me -> is an ongoing cost where I don't see why I should keep paying it.

1 Like

What does -> vs : have to do with eliding {}? They seem orthogonal to me.

6 Likes

I'd argue that the signature/body confusion becomes significantly clearer if you never have one-line functions.

For instance, in your example, this reads pretty clearly to me:

fn foo(x: Something, y: SomethingElse) -> Foo {
    Foo { x, y }
}

The first line is the declaration, and the inside is the body. It is a bit more confusing when the declaration spans multiple lines, but avoiding that is usually possible. And when it isn't, like with where clauses, the where clause gives extra space between the return type and the body.

I wouldn't be against a Scala-style function declaration like you've stated, but I think the usefulness becomes very limited once you start writing bigger functions. For a bunch of getter methods, it would be great! But anything more than one line, and it starts to end up a bit weird. Given that limited usefulness, I think changing function formatting to clearly separate them could end up better in pratice.

Given that rustfmt always expands single-line function definitions into multi-line ones, I've never run into this problem in practice. Yes, it's a solution using formatting standards. But is formatting really a bad solution to a readability problem?

3 Likes

Have one-line functions with = been discussed before? Otherwise I'd like to start a new thread.

2 Likes

If you introduce consistency between function and variable syntax, I guess it makes more sense to go 100% instead of 50%, but you are right, it's not that related to the topic at hand.

I think the point is that it encourages writing simple, straight-forward functions by rewarding the author with saving him/her 1 or 2 lines of code. In my experience, this this worked out as expected.

Anyway, as mentioned above, my comment was probably off-topic, so I'll stop.

Couldn't they be implemented as a macro?

Probably not. Also, I would assume that it would be desirable to have less syntactic options, not more, in general. :slight_smile:

Not sure why you'd say that. It works out very well.

6 Likes

Generally, : is used to denote the type of the thing that comes before it. I think it could be argued that in order to be consistent, in the case of functions this would have to be the type of the whole function itself, not just the return type.

8 Likes