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

Actually, what I intended to get at was that if you would consistently replace -> with : in something like

type Something = impl Fn(Foo): Bar;

it would be ambiguous to me whether Bar is the return type, or a (further) type constraint on the callable itself.

(Apart from fn foo(f: fn(Foo): Bar): Baz having too many colons in general for my taste.)

1 Like

Disclaimer: The following is purely subjective.

Personally, even though I mostly like Go in general, I think the function sytnax is pretty hard to read... Method signatures can have as much as three parentheses groups and it's basically a pile of idents and types, where you need to process the order to understand what's what. Kind of like C, but with added complexity (such as the receiver, which has no consistent name like self and instead is custom-named in every function). The -> in Rust is perhaps somewhat unusual in an imperative language, but it really makes the return type easy to spot and gives the function signature some structure...

12 Likes

Note: the following thoughts are quite unorganized. I tried to condense it the best I could, but that really isn't enough.

It's worth noting that in some functional languages, this is intentional. Is there really a difference between "the function f applied to a u32 and u32 results in u32", and "the function f applied to a u32 and u32 has type u32"?

If the languages encourages functional programming and pure functions, there really isn't a difference. At the extreme, in Haskell, there is no real "running" a function - the result of applying parameters to a function just directly is the result type. In this kind of language, I think : makes total sense: the important thing about the function is what you get out of it, and thus you can really think of "function + parameters" as equalling the result, and thus being a type. : gives values types, and the function + parameters has a type, so this seems reasonable.

If the language favors impure functions, mutable variables, etc., then the distinction between a function and its result starts to make more sense, I think. Then "executing the function" becomes a useful thing to reason about, and if you execute the function, having it produce a result when given parameters makes more sense than its type applied to parameters being the result.

Since Rust has mutation, and values it greatly, I think it belongs to the latter set of languages - those favoring impure functions. Thus talking about executing a function, and that function producing a result makes more sense than having the function applied to parameters equal a result. The -> here could really represent that "producing", as opposed to : which treats the combination of function and parameters directly as the result.

Anyways, sorry to ramble! Let me know if it makes any reasonable sense?

4 Likes

I like these thoughts. They might break down a bit around function types (or similarly for Fn traits). Something like fn(A, B) -> C does closely resemble function signatures/types in math or Haskell (and probably some other functional languages) syntactically, even though in Rust this kind of function is of course impure, too.

1 Like

Well, I think you could simplify things by looking at it slightly differently: In Rust, every function takes exactly 1 parameter: a tuple of arity between 0 and n, and gives a single result type. Then you'd get the types in Haskell-like notation (apologies in advance to people actually versed in Haskell):

foo :: Tuple -> Result for the function foo taking a Tuple and returning a Result

foo Tuple :: Result when a tuple is applied to the function

In the parameter list you put additional constraints on the tuple elements.

There is no real semantic reason. However, syntax does have value (do you see many people writing lisp :wink:ΒΉ?). I believe that the reason for using the arrow, as discussed above, is threefold:

  1. Mathematically, specifically in type theory, an arrow represents a mapping between types. All Rust functions (ignoring closures) are named, and their signatures define the type mapping. Using an arrow to create a signature that defines a type mapping is common in many other functional programming languages, including languages in the ML family which inspired Rust. Additionally, if we extent the type theory argument, we can see that types are explicitly annotated using value: Type. It doesn't make sense to use : to denote type signatures. In the narrow sense, : means 'is of type'. Although it is true that a function fn _(x: usize) -> String 'is of type' String, that's not the full story. To produce that string, a transformation has to occur, and a value of another type (in this case usize) is required.
  2. Lexically, the presence of an arrow is clearer than the absence of one. A function, say:
    fn count_vowels(string: &str) -> usize { ... }
    
    can be read as: 'the function count_vowels takes a string and produces an integer.' The arrow can be read as 'produces', 'becomes', or 'maps to', which makes sense when reading code literally.
  3. Visually, an arrow helps maintain consistency and shows the presence of functions. For example, A higher-order function might have the signature:
    fn map_vec<A, B>(f: fn(A) -> B, v: Vec<A>) -> Vec<B> { ... }
    
    This consistency shows that this function takes a function; the arrow stands out in the argument. If anything, we should be upset about the lack of arrow notation in closures :upside_down_face:!

In conclusion, you have to remember that Rust is more 'ML in C++'s clothing' than an exact analogue to a C-style language. Rust has been inspired by functional programming languages, which generally include an arrow to notate the mapping produced by a function.


ΒΉ: As someone who writes lisp on a regular basis, I have permission to make this joke.

7 Likes

Closures do use arrows if you bother specifying the return type. You can be totally explicit:

iter.map(|foo: Foo| -> Bar { foo.into() })
5 Likes

rustlang's documentation uses "Source Code Pro" font in which -> looks as an arrow. But if you use other monospace fonts like what this forum and github uses, it looks bad.

I don't know about any other monospace font in which -> look good.

math uses 'arrow' not - and > together

In haskell, you need to first declare and then implement. so it make sense to have and arrow like notation in type signature.

But in rust you have to declare and implement the function at the same time. If rust did it like what haskell does, that would make more sense to use an arrow notation.

some of you said that you like to have : before return type.

but i don't think that is a good think to do. That will increase confusion.

As i know that function don't have type, they take value and return value, a machine.

At least -> serves as a decorative purpose.

Unfortunately, most keyboards don't have an β†’ key*. I've look at popular non-QWERTY layouts, and none of them have dead keys for typing arrows, so while using Ø for the Never type would be feasible in QWERTZ T2 and Colemak, using arrows in the syntax isn't really an option.

* the cursor navigation keys are not relevant; we're talking about characters that can easily be typed.

3 Likes

what if rustfmt replace -> with an arrow

At that point just use a font that combines those two characters into something that looks like an arrow

4 Likes

This is a legitimate concern. However, I think it's a lot smaller of one then you're making it out to be. For instance, we can use google fonts to look at what -> looks like in the most used monospace fonts: Browse Fonts - Google Fonts

Of this, I'd say IBM Plex Mono, Cousine, Cutive and B612 look bad, but the remaining 17 all align the - and >, and look good. This is a limited sampling, but from this, I know that there are at least 17 popular monospace fonts where this looks "right."

Besides regular fonts though, there's another (possibly better) solution: configure your editor to use font ligatures. With ligatures, any -> in source code will always be displayed as a connected arrow. I think this should be very similar to your suggestion:

But with the advantage that -> is always displayed as an arrow, even when immediately typed, and that we don't have to actually change the source code.

I think using ligatures is also what @Nokel81 is suggesting?

2 Likes

Yes, I just couldn't remember the name of it.

I can easily type β€˜β†’β€™ as AltGr+I.

Not sure why you’d want to use β€˜Γ˜β€™ for the empty type; β€˜βˆ…β€™ would be more appropriate. Although the empty type is usually denoted by β€˜βŠ₯’ anyway.

1 Like

There is no standard key combination on a US Mac keyboard that generates an arrow. When I type the equivalent on my US Mac keyboard I get β‰₯I. Other keyboards for other languages will generate different combinations.

I started Rust before Go, and I found Go style harder to read. A friend started Go before Rust, and he finds Rust style harder to read…¯\_(ツ)_/Β―

8 Likes

It's come up in passing many times. i don't know if there's ever been a dedicated discussion about it.

Personally I've never really been a fan. Even in C# -- where the language isn't expression-oriented by default, so using => saves a return -- I'm not a big fan of it in functions, because it's just another thing to argue about in formatting discussions. (I do like it in C# for get-only properties, though.) So in Rust when the difference between { 4 } and = 4; is just a space, I'm not convinced that it pulls its weight.

1 Like