Let's fix `if let` syntax

I don't think that is true (except for expressions). It should be able to handle the rest (depending on the implementation).

Even if it were true, we still have the current if let for those things when needed, right?

I've been following this discussion with interest but have yet to see anything I would be happy with. The way it works now is I think fine, easy to reason about, a good Swifty way to deal with optionals. E.g. this is from code I was just editing:

if let rect = geom.leftRect {

and is vastly preferable to e.g. having this pattern:

let rect = geom.leftRect
if rect != nil {

littered throughout my code. As the two sides of the '=' are different there's no real way to shorten it, beyond the abbreviating I've done. Which is how I'd deal with the examples in the original post, e.g. in my code fooAutomationViewController might become controller, or favController if there was a need to disambiguate it from other controllers.

4 Likes

Here are some ways to make a variable nil after checking if it's not nil

var _returnedAlready = false
/// returns `.some` once, and then always `.none`
func returnOnce() -> Int? {
    if _returnedAlready {
        return nil
    } else {
        _returnedAlready = true
        return 123
    }
}

class C {
    var variable: Int? = 123
}
let c = C()
var global: Int? = 123
func unrelatedFunction() {
    c.variable = nil
    global = nil
}

/// This function takes `Int`. What happens if you pass the original variable which is now nil?
func takesInt(_ arg: Int) { }


func computedVariable() {
    var variable: Int? { returnOnce() }
    if unwrap variable { // We get into the scope, because `variable` is `123`
        takesInt(variable) // oops! variable is `nil` here
    }
}

func structProperty() {
    struct S { 
        var variable: Int? = 123
        mutating func f() {
            variable = nil
        }
    }

    var s = S()
    if unwrap s.variable {
        s.f()
        takesInt(variable) // oops!
    }
}


func classProperty(c: C) {
    if unwrap c.variable {
        unrelatedFunction()
        takesInt(variable) // oops!
    }
}

func global() {
    if unwrap global {
        unrelatedFunction()
        takesInt(global) // oops!
    }
}

func propertyWrapper() {
    @propertyWrapper
    struct Wrapper {
        var wrappedValue: Int? { returnOnce() }
    }

    @Wraper var variable: Int?

    if unwrap variable {
        takesInt(variable) // oops!
    }
}

Even if it were true, we still have the current if let for those things when needed, right?

This thread is about making something better than if let. It would be a shame if the result is that I still need to use if let 90% of the time. :(

3 Likes

My suggestion would be to use a keyword here that we can reuse in other similar context and not just unwrapping.

if using let someThing {...}

We could then use this for other cases like

if using let someResource = resourceProducer() {
// unwrapped 
// after this scope the resource is disposed 
}
1 Like

I would suggest probably best to peel that off into another discussion if you're keen on resurrecting that topic, since it's pretty distinct from the direction of this discussion in the last 100 or so messages and @ensan-hcl earlier already summed up why.

1 Like

Syntax like if fooViewController? {} looks more magical than needed for me.

We want to save clarity, but do the syntax shorter and don't repeat spelling of variable name.
So, what about such variants?

if let unwrap fooViewController?, fooViewController is UINavigationController { 
  ...
}

if var unwrap fooViewController?, fooViewController is UINavigationController {
  ...
}

guard let unwrap fooViewController?, fooViewController is UINavigationController else {  return }

This syntax is similar to current, and might be understandable.
It also allows var declaration in the same manner as let.

1 Like

I really like this! Thank you for suggesting this.

I felt that all syntax except declaration tempt us to write if let x.y {} and the similar. Your purpose to define new constant is the key of why you don't mistakenly write expressions like let x.y = 42 in declaration. When it comes to if let x {}, your purpose is unwrapping target value. What you write is the name of target value, not the name of new constant. No matter what keyword is used, even shadow, you would lose feeling that you are defining new constant and think 'it can unwrap target value'.

In order to avoid misleading, new syntax should hold its 'declaration-ness'. Such syntax doesn't make us feel 'it's the way for unwrapping'. if let value = itself! {} (mentioned by @Matt_McLaughlin) or if let x = _ {} (mentioned by @benrimmington) or if let x = * {} is perfect solution from this aspect. It holds declaration style, has less repetition, and has many usage other than unwrapping.

As shown below, I have some concern about this, but I think it's best solution suggested in this thread.

  • Renaming will be difficult with this: let's consider func square(x: Int) -> Int. If you use Xcode select 'Refactoring -> Rename', you can change function's label into number from x. When there is code let x2 = square(x: *), Xcode must change it as let x2 = square(number: x). This would make renaming more difficult.
  • Details should be discussed:
    • When you write self.x.y = *, does * mean variable y?
    • When you have property tuple: (x: Int, y: Int), and you write self.tuple = (*, *), does (*, *) mean (x, y)?
    • When you have function func range(from num1: Int, to num2: Int) -> Range<Int>, does range(from: *, to: *) work? What it means is range(from: from, to: to), right?
  • Autocomplete would not work.
1 Like

I guess I had a different mental model of how the implementation would work

Something closer to:

if var _x = x as? S {
    /// Do stuff to _x
    x = _x
}

I can see how people wouldn't expect that though...

FWIW, as someone who only learned Swift about a year and a half ago, I have to say that every single suggestion in this thread bar one would have been much more confusing to me than if let x = x {

The one that I wouldn't have found as confusing is if let x? { because it sort of reads similarly to optional chaining.

19 Likes

Interesting, if let x? is confusing to me, because yes, it is similar to optional chaining.
Additionally it reminds me of this kind of situation (this may seem pointless though, postfix ? tends to make me think it'll end in optional):

var value: NSObject??

if let value = value! as NSObjectProtocol? { ... }

However technically I can see there are people if let x? is clearer. (Like, if let + optional (chain) = unwrapped)
I wonder how it would work with Xcode completion, though.

Following this thread shows really what for me is the meaning of “terror of choice”!

Somebody here said that it would be best to choose something that can be generalized and so my favorite is

if shadow let value { ... }

And “shadow” should be available for all let and var statements

shadow let x = ...

One possible use is a rule for a linter that all shadowing should be made explicit. In my experience, I would even introduce this as a hard rule.

3 Likes

I encountered

if let fooViewController = fooViewController { }

not long ago so I remember well my surprise before I understood the meaning.
But then it actually helped me understanding optionals.

Looking at the two examples given in the initial post.

If have fooViewController {…fooViewController.view… }
If fooViewController? { …fooViewController.view… }

The way I understand it, fooViewController is an optional outside the brackets, and a non optional inside the brackets.

If my understanding is correct it would mean:

  1. The fooViewControllerinside the brackets is an « automatic » creation of a non optional version of the optional fooViewController outside the brackets?
  2. The type of fooViewController is changed to a non optional version. A type change of fooViewController?
  • Case 1: I am certain that it will be even more confusing to newcomers than the old ways. After all the science of Aristotle has taught us that spontaneous generation does not exist. :-)
  • Case 2: Is more scary. With a type change it will need to be « changed back » to optional at the end of the brackets. Otherwise fooViewController will have a dual personality (dual type). If it does not enter the brackets it will remain an optional after the if. But if it enters the brackets it will change to non optional, exiting the brackets as non optional.

In my opinion this would be vastly more difficult to explain and justify. Inconsistent so more difficult to grasp than the current way of doing things. Especially since optionals are such an important element of Swift.

1 Like

It doesn't merely look like optional chaining, it also looks like matching an optional value in a switch statement.

let value: Int? = nil
switch value {
case let value?:
    print(value) // value is `Int`
case let value:
    print(value as Any) // value is `Int?`
}
2 Likes

Umm no it doesn't, yes I agree.
However it kind of reminds me of it and feels inconsistent, personally.

And it feels a bit magical.
For me, if let value? { .. } sounds like if "value" is "optional" then ... .
So I'm on with if let value { .. } if new keyword shouldn't be introduced.


This is the very case I intentionally avoid because it confuses me at a glance.
Practical case would be something like this:

asynchronousCall(completion: { (_, error: Error?) in
    switch error {
    case let error?:
        print(error) // error is `Error`
    case let error:
        print(error as Any) // error is `Error?`
    }
})

I'd just rather write in either of style:

asynchronousCall(completion: { (_, error: Error?) in
    switch error {
    case let .some(error):
        print(error) // error is `Error`
    case .none:
        print(error as Any) // error is `Error?`
    }
})

// OR

asynchronousCall(completion: { (_, error: Error?) in
    if let error: Error = error {
        print(error)
    }
})

But to be clear I'm not saying something like you're wrong or I'm wrong, or someone is wrong.
In the end, this is how individuals feel about it I guess. And I felt every single ideas from this thread have each own points.

if let value? sounds like a shorthand for if case let .some(value) = value { .. } so it has a point.

So if if let value? { .. } is implemented in the end, I'll just try to get used to with it or just keep using if let value = value { .. }.

BTW I'm on the both sides ( but without postfix ? ).

This is the first suggestion I could back.

Just because the new keyword ‘using’ could be used for a bigger language feature to bring other namespaces into local scope. This has huge potential and is something I would love to see in Swift someday.

What’s wrong with the optional map function if you want a shorter if let?

let foo: Int? = 42

... = foo.map { /* do something with non optional Int */ }
1 Like

Is it allowed to write this here in the forum: I think there is nothing wrong with the current if let -syntax?

15 Likes

Agreed. All of the proposed syntax seems worse that what we have. All that to avoid repeating a name. If that hinders refactoring then that is a tooling problem, not a language problem.
Also nobody is forced to reuse the exact same name. Many of the examples accompanying the introduction of Swift used if let assumedBar = fooBar.

If - in this case - new syntax won’t enable new language capabilities, then it’s a -1 for me.

11 Likes

Thanks for the positive words!

RE: renaming... agreed but that can probably be handled by tools but also, the compile would fail and it would be pretty obvious what was wrong and what to do? (vs. the constant pain of having to double up all this stuff all the time). Lots of if let x = * referencing non-local vars of the same name would be annoying this case without tooling for rename support for this syntax.

With expressions like self.x.y = * ... IMO if deep expressions like that are supported by the feature, it would have to be the last token only, so y in that case.

Re: autocomplete... what would not work? You avoid a huge need for autocomplete just by having the = * (or similar) syntax.

I don't think introducing a whole new sigil usage for this case is worthwhile. Such uses in Swift are when there is a very common and fundamental need i.e. ? for optionals, & for inout. To introduce a new meaning for * to facilitate this case doesn't clear the bar.

(same goes for let '' = x)

14 Likes