why ->
is used?
instead of just the return type like Go
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:
f(R) -> R
()
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.
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
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 ->
?
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.
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.
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. 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.
What does ->
vs :
have to do with eliding {}
? They seem orthogonal to me.
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?
Have one-line functions with =
been discussed before? Otherwise I'd like to start a new thread.
Have one-line functions with
=
been discussed before? Otherwise I'd like to start a new thread.
Couldn't they be implemented as a macro?
(The syntactic connection between functions and lambdas is overrated and never works out in practice.)
Not sure why you'd say that. It works out very well.
So, yeah β using
:
like modern languages would have been way nicer and less confusing
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.
Generally,
:
is used to denote the type of the thing that comes before it.
Exactly. The thing in front of the :
is however (syntactically) not just the function. It is the result of applying the function to some parameters (in the form of some variables). The proposed syntax from @anon2808951 is:
fn foo(x: Something, y: SomethingElse): Foo // { .. } OR = ...
This reads as: foo(x,y)
has type Foo
. Ascribing a type to foo
itself would, in a Rust-like syntax, need to look roughly like
foo: fn(Something, SomethingElse) -> Foo
It is however not an uncommon or particularly bad mistake to mix up a function π with the result of its application to a variable, π(π₯). People still say things like βthe function πΛ£β all the time and AFAIK before the 20th century, most mathematicians tended to use π and π(π₯) interchangeably in general. However at that time, type systems where not necessarily a thing yet either.
Nowadays, we have a set theoretical foundation of maths, where β plays more-or-less the role that :
does in some programming languages and in type theory. So in math, for a function π you could write a function signature like π: π΄βπ΅, but the βtypeβ of this function could also be expressed in terms of sets, usually with the notation πβπ΅α΄¬. This π is distinguished from its image π(π₯) for some π₯βπ΄. For π₯βπ΄ youβd have π(π₯)βπ΅.
fn foo(x: Something, y: SomethingElse): Foo // { .. } OR = ...
This reads as:
foo(x,y)
has typeFoo
.
TBH, this gives me the slightly uneasy feeling of mixing function declaration with function application, but I can't say whether that actually matters or not. But note that for the type of foo
you had to resort to using ->
for the return type again.
For a different example, how would the following look in the new syntax?
fn foo(f: impl Fn(Foo) -> Bar) -> Baz
For a different example, how would the following look in the new syntax?
fn foo(f: impl Fn(Foo) -> Bar) -> Baz
The arrow for the Fn
trait would not change, so it would be:
fn foo(f: impl Fn(Foo) -> Bar): Baz
Also note that (as I mentioned in an earlier comment) Rust syntax does not resemble maths very much anyways, and Iβm not saying that Iβm personally in favor of changing the ->
to :
. Iβm just saying that there is a way in which using :
would make a lot of sense. I personally find the way that Rust syntax currently works pretty readable in this regard and as long as itβs readable Rust can do whatever it likes to do.
this gives me the slightly uneasy feeling of mixing function declaration with function application
I think this is the case because the function declaration in Rust (or C or similar languages) syntax is based on function application syntax, that is, you write arguments comma separated, in parentheses, after the functionβs name.
In more detail: declaration and definition are actually intermixed in Rust, into one syntactic construct, which is actually mostly just (mathematical) function definition syntax plus some type annotations on the arguments and without an equals sign. Compare my (already mentioned) earlier comment on mathematical function definition syntax; function definitions simply contain an actual function application (with variables as arguments) on the left hand side of the βequalsβ sign, so thatβs the fundamental reason why, as mentioned, βfunction declaration syntax is based on function application syntaxβ in Rust.
fn foo(f: impl Fn(Foo) -> Bar) -> Baz
I think you meant instead:
fn foo(f: fn(Foo) -> Bar) -> Baz
The following code is legal today:
let foo: fn(f: fn(Foo) -> Bar) -> Baz = |f| { .. };
So interestingly enough fn foo
syntax can be viewed as a sugar for:
const foo: fn(fn(Foo) -> Bar) -> Baz = |f| { .. };