[Pitch] Implicit `return nil` for Closures with optional return type

Implicit "return nil" for Closures with an optional return type.

Introduction

We propose adding implicit return nil when Closure has optional return type and no other return statement is executed.

Motivation

As an example, check the following computed property:

    var roleAndCompany: String? {
        if let role, let company {
            return "\(role) @ \(company)"
        } else if let role {
            return role
        } else if let company {
            return "@ \(company)"
        } else {
            return nil
        }
    }

With the proposed feature, we would be able to omit last else block with "return nil", since Closures with optional return types will implicitly have a nil value returned by default if no other return statement is called.

This would align with how optionals work in Swift. For instance, if you declare an optional property and never assign any value to it, its implicit default value is nil.

With proposed solution functions and computed properties with optional type will work the same and have nil value by default with no need for dedicated return nil statement.

This feature is present in Python, Ruby, Javascript, Lua and few other languages.

Other Information

I started discussing this feature on old thread started by @Logan_Sease, and I was suggested by @Jon_Shier and @xwu to create a dedicated topic.

1 Like

With the help of SE-0255, SE-0345 and SE-0380 you could syntax optimise that fragment to:

var roleAndCompany: String? {
    if let role, let company {
        "\(role) @ \(company)"
    } else if let role {
        role
    } else if let company {
        "@ \(company)"
    } else {
        nil
    }
}

Frankly I don't think omitting the last two lines is a good thing here.

7 Likes

@tera I am fully aware of SE-0255, SE-0345 and SE-0380. I wrote the example code with return statements for clarity purposes.

Do you have any specific concerns, or have in mind some specific problems implicit return nil feature for functions with optional return type would cause in your daily work?

I think this feature would align pretty well with recent Swift language features and especially with features implemented with proposals that you mentioned.

Two obvious reasons:

  1. nil is not that so special. You'd probably want to do the same for other values, making the language more complicated than needed:
func foo() -> Int = 42 {
    ...
}
  1. The feature would make it too easy to forget to return what you need to return, as compiler would "help" you forgetting it:
func stringToInt(_ string: String) -> Int? {
    ...
    // ok?! should have been `return Int(string)`
    // there was no error or warning.
}
7 Likes

I thought this was going away. I need to update myself on this.

Regardless, as much as I sometimes want this feature, I am hesitant to eliding more Swift especially when it comes to code flow.

I do believe it isn’t active harm either way. And I do believe such a feature could be easy to explain to new programmers.

If implicit nil for variables go away, I would even more against this proposal.

Another way to write the example is:

var roleAndCompany: String? {
    switch (role, company) {
    case let (role?, company?):
        "\(role) @ \(company)"
    case let (role?, nil):
        role
    case let (nil, company?):
        "@ \(company)"
    default:
        nil
    }
}

For consistency it would need to be permitted to omit the default: case. But Swift doesn't allow that for good reason - forgetting cases, or simply forgetting to finish a method implementation, are common programmer errors.

Omitting the return keyword is different, since doing so introduces no ambiguity w.r.t. the code's meaning and is much less likely to permit a typo.

So I tend to think implicit return values are not wise.

9 Likes

Please no. "Implicit" code make things harder to read and understand.

Also, a better way to write the example computed property is with a switch, that would require (for good reason) exhaustive case checking, so a nil would still be needed, even without return in Swift 5.9.

16 Likes

-1

This would open doors to accidentally write unintended behavior and even silence diagnostics.

7 Likes

Strong negative for this. Swifts "explicitness" is one of its core feature. Implicit control flow is chaos because it makes it harder to reason about the code itself. You simply can "forget" this function returns nil as a default value when debugging

This would align with how optionals work in Swift. For instance, if you declare an optional property and never assign any value to it, its default value is nil.

That's not quite true. Optionals are initialized with value of nil for the same reason Int is initialized with 0. So it would not be initialized "garbage", for safety.

This feature is present in Python, Ruby, Javascript, Lua and few other languages.

And in all of those languages control flow is a mess, especially in python and ruby

7 Likes

My proposal is only related to Closures with optional return value, your first example does not have optional return type.

Optional are kind of special since optional properties do have implicit value of nil set upon declaration.

I wouldn't mind at all getting nil instead of compile error in your second example.

Since you provided empty block, you would get what you write, my professor used to say: programming language won't read your mind :-)

I'm not sure what are you referring to, but yeah implicit nil value for optional properties is why I would like this idea implemented in first place. And yes, I do use this feature in other languages, and I do find it useful and logical.

My proposals is only about Closures (blocks) with optional return, omitting switch default case is cool idea, but its not within the scope of this proposal

To be honest I find this feature less evil in this regard than implicit return for one-liners. I think there were many more compromises needed to add such a language feature, and as well I understand all of the benefits.

Optional properties already have implicit default value of nil, I don't find it any more confusing if Closures with optional return type had implicit return nil.

How? Implicit return for one liners is definitely a much simpler concept that an implicit return in a complex conditional structure.

Only if they are var, and even that is not great, in fact you can opt out of the thing entirely by specifying, for example, Optional<Int> instead of Int?.

Also, I fail to see the relationship between complicating and obscuring control flow with implicit returns, and the fact that var optionals are initialized with a default nil value.

1 Like

Not commenting on the main idea but on the return type from if statements as expressions.

 // `else { nil }` can be implicit
let optional: T? = if let t { t } else if let otherT { otherT }

That's something the VerseLang has.

It would cover the example from the motivation at least:

var roleAndCompany: String? {
  if let role, let company {
    "\(role) @ \(company)"
  } else if let role {
    role
  } else if let company {
    "@ \(company)"
  }
}

That doesn't compile... (and I don't think it should).

This is better:

var roleAndCompany: String? {
    ...
    else {
        return // meaning the same as `return nil`
    }
}

Similar to how "return ()" is equivalent to "return" in Void functions.
Still -1 from me even in this form.

2 Likes

I didn't say it should compile. :smiley: If you have a non exhaustive if expression, it's natural to implicitly transform its return type into an optional.

let number = if someCondition { 42 } // what's the return type?

Naturally it should be Int? as the else condition is not covered. It either has a value or not, which is what an Optional does.

We always have to answer the question: What does it enable us to do?

Aside from convenience here and there you could perform a flatMap like operation without the need to explicitly nil return every possible branch.

let transformedValue = if let nonOptional = value {
  if let otherThing = nonOptional.someOptional {
    transform(otherThing.value)
  }
}

// instead of
let transformedValue = if let nonOptional = value {
  if let otherThing = nonOptional.someOptional {
    transform(otherThing.value)
  } else {
    nil
  }
} else {
  nil
}

The else { nil } is just redundant boilerplate code for if expressions.

If we'd do the same for non exhaustive switch expression then we can omit default: nil to achieve the same.

let value = switch something { 
case .a:
  "a"
case .b:
  "b"
}

value is of type String? as we used a non exhaustive switch expression.

I personally think it's a natural extension for if and switch expressions, wether or not Swift will add those is a different story.

I found this example highly confusing, to even start comprehending what its "syntax optimised" version could be... what are you even trying to do here? I think you are mixing "if let x = ..." and "let x = if...". Please show the whole compilable example.

1 Like

It's just something quick from the top of my head. Sorry if it confuses you. Also I'm not trying to do anything, it's just a hypothetical example, nothing more.

If I'm not mistaken the above could be rewritten as this:

let transformedValue = value
  .flatMap(\.someOptional)
  .map(\.value)
  .map(transform)

Assuming this is what you meant:

func transform() -> Int? { nil }
var condition1 = true
var condition2 = true

    let foo: Int? =
        if condition1 {
            if condition2 {
                transform()
            } else {
                nil
            }
        } else {
            nil
        }

where you want to write instead:

    let foo: Int? =
        if condition1 {
            if condition2 {
                transform()
            }
        }

Note that you could optimise it to a simpler:

let foo = (condition1 && condition2) ? transform() : nil
1 Like