[Pitch] Last expression as return value

I just want to second this comment.

Since this has been resurrected, I’ll write as well to just add another voice from a random developer: big no from me, for all the reasons better explained above

4 Likes

I have heavily used this syntax in Scala. All it did was make my code significantly less readable.

There is a strong case to be made for this feature iff you are programming in a functional programming language.

My take is that adding this syntax to an existing multi paradigm language is a very bad idea.

It provides a small convenience to experienced programmers who are familiar with functional programming style at the expense of making such code significantly less readable for novice and intermediate programmers, especially those who have never done functional programming.

12 Likes

If your team prefers to avoid a particular Swift feature, you can enforce that with a linter. True, when working with other people's code, you’ll need to tolerate different choices and linting preferences or reformat it to match your own – but that's also true for any other feature.

4 Likes

If this has generated such a big debate on swift forums, a place where only a select few actually congregate, I can only imagine the debates this will trigger in linter rules meetings. Not to mention that regardless of your own team’s preferences there will be third party code you’ll have to read/reason about that has the exact opposite choice made. And I definitely do not agree that last expression as return value is just any other feature. return is quite a key … well, keyword. No idea if this is possible or not but personally the only way I would see this being optional would be something akin to @discardableResult

4 Likes

I still believe this is a great feature provided it is disabled for discardable results:

    func cook(vegetable: Vegetable) -> Vegetable {
        print("Bzzzz")
        vegetable.cook() // ❌Error: discardableResult functions need explicit return!
    }
    func cook2(vegetable: Vegetable) -> Vegetable {
        print("Bzzzz")
        Vegetable() // ✅ fine
    }

thus fixing the regression mentioned in the headline post and making it less non-obvious. This feature naturally promotes "single expression rule" to "last expression rule" (single line is the last line as well) thus making the language conceptually simpler.

Yeha, soon to be the all time winner :person_facepalming:

1 Like

The top two are conversations on the same topic, so it is already the all-time winner. But sure, we can add some more replies to extend the lead.

7 Likes

No offence intended, but I really abhor this feature.

For example, how do I debug the above code? :slight_smile:

Why is it so hard to write a return <Expression>?

PS: I am not a touch typer.

9 Likes

I like the motivation here. However, one downside is that it would make it impossible to judge whether or not a return keyword is necessary in a position without full type context. This is relevant for code formatting tools like swift-format / SwiftFormat, which only operate on syntactical information available per-file. Everywhere else in the language, whether or not a return keyword is necessary is purely syntactical (doesn’t require type checking).

2 Likes

That's also true about the "single expression return" rule, though – and thus not an additional issue – right?

Not sure what you mean, here are some examples:

// Only one expression, so the `return` can be removed
func myFunction() -> String {
  return makeString()
}

// Multiple expressions, `return` cannot be removed
func myFunction() -> String {
  print("my function")
  return makeString()
}

Whether or not the return is necessary can be determined purely syntactically (e.g. parsing the expressions). Tools like SwiftFormat have autocorrect rules that remove the return keyword in the cases where the body is only a single expression.

Following this proposal, this would now be valid unless makeString() is @discardableResult

// `return` keyword optional for last expression,
// as long as `makeString()` is not `@discardableResult`.
func myFunction() -> String {
  print("my function")
  makeString()
}

Unlike the cases above, it would be impossible to know whether or not this is valid without full type context (we have to know whether or not the signature of makeString() is @discardableResult or not). This would make it infeasible for existing code autocorrect tools to apply this style preference (omit return keywords here).

Supporting syntax-based code autocorrect is of course not a requirement, but this is the sort of style concern that folks are used to using code autocorrect to enforce in their codebase.

2 Likes

You are right. This means that:

  1. either @discardableResult should not make a difference (as it doesn't in the original proposal), or
  2. linters won't support a rule similar to redundantReturn in case of multi statement bodies.

Not ideal but hopefully not a show stopper.


Edit:

  1. Make compiler itself deal with those warnings (off / omit returns / require returns) as pitched here – compiler has all the information needed including types and @discardableResult attribute.

This is really only a problem for closures anyway. In a normal function the return type is statically known without inference, so a @discardableResult function could only be implicitly returned if it was both the final expression and matched the return type of the function.
And @discardableResult functions already aren't implicitly discarded in a single expression closure.

But with multi-statement implicit returns you'll get a choice between:

foo {
  _ = discardableFunc()
}

and

foo {
  discardableFunc()
  () // Implicitly returned empty tuple
}