Type narrowing

Just noticed that Swift doesn't narrow types.
For example, variable a is Int?, and getting into the if statement if a is not nil, so I think compiler can assume a is Int, but why I still need to unwrap a inside if statement? I know I can use if let statement, but I'm wondering why Swift doesn't support type narrowing like Typescript.
Any plans to support this or are there any reasons why Swift can't do this?

var a: Int? = 10
if a != nil {
    var bar: Int = a // error: Value of optional type 'Int?' must be unwrapped to a value of type 'Int'
}

// of course this works
if let unwrapA = a {
    var bar: Int = unwrapA
}

Type narrowing works in Typescript Playground

const a: number | undefined = 0;
if (a !== undefined) {
  const bar: number = a; // type narrowing works in Typescript
}

BTW you can do if let a now, which is even shorter than if a != nil.

2 Likes

There have been several discussions in the past about this feature:

November 2016:

October 2020:

September 2022:


Notably, in the Nov 2016 thread, Chris Lattner wrote:

There were also questions about how it would interact with overload selection, how it would work with stored class properties (that might get changed while your code is running), and how it would work with computed variables.

Personally, I’ve never really wanted a feature like this. if let works fine for me. I’ve never used a language with this feature before, though, so maybe I just don’t know the benefits.

3 Likes

@1-877-547-7272 You just jogged my memory on this...

Type narrowing doesn't work well for mutable stored properties and computed properties. There's no guarantee that it being non-nil at the time of first check will mean it's still nil when called the second time.

See Yelling at a rocket after launch: checked optionals - #9 by AlexanderM

4 Likes

Oh, and TypeScript's type narrowing is basically completely wrong when dealing with instance variables:

class Demo {
  a: number | undefined

  constructor() {
    this.a = 123;
  }

  main() {
    if (this.a !== undefined) { // Time of check
      this.doSomeArbitraryThing();

      const bar: number = this.a; // Time of use
      console.log(bar); // undefined!
    }
  }

  // Some arbitrary computation, which can change the state of the world
  // Invalidating the "a isn't undefined" conclusion we made in the `if` above.
  doSomeArbitraryThing() {
    this.a = undefined
  }
}

new Demo().main()

It's a classic Time-of-check/time-of-use bug, and unfortunately, the compiler just straight-up lies to you lol

5 Likes