Treat optional as non-optional after being initialized

Once swift knows you have set a non-optional value to an optional variable, then the compiler no longer needs to treat that value as optional.

var task: Task?
func createAndPerform() {
    task = Task()
    task?.delegate = self
    if let task = task {
        perform(task: task)
    }
}
func perform(task: Task) {
    task.perform()
}

vs

var task: Task?
func createAndPerform() {
    task = Task()
    task.delegate = self
    perform(task: task)
}
func createAndPerform2() {
    let task = Task()
    task.delegate = self
    self.task = task
    perform(task: task)
}

There's a few places within my app that has code like the above. It seems like a bug when I create a new non-optional value, but then the compiler is telling me Value of optional type 'Task?' must be unwrapped, even though it is already unwrapped. I know today there are a few ways to achieve this, but all solutions are just workarounds for the error being produced.

When the variable is not scoped to the code which is manipulating it, Swift can't prove, at compile-time, that the variable is not modified by another thread after assignment within the function.

5 Likes

Generally, I use an implicitly unwrapped optional if there is a value that I can't set during initialization, but I know it should never be nil in use, and that it is a programming error if it is accessed while nil.

For me, that is the best solution for this situation, since it allows you to treat the property as unwrapped everywhere else in your code and it crashes if you have made a programming error.

If it is possible to structure your code so that the values are available at initialization time, or lazily created when needed those are other approaches to avoid this.

If you used

var task: Task!

in your example that would keep you from having to unwrap everywhere else and enforce the notion that if it the value is nil when used, it is a programming error. (As opposed to something like task?.perform() which would just fail silently).

5 Likes

I wonder if a restricted version of this feature would make sense? Kotlin has some rules around its smart cast functionality for example so I wonder if this feature could be implemented with some rules around it (though I haven't given much thought to it).

1 Like

In general it would be bad for tooling support. If a variable can change its type in the same scope it is defined, when you alt+click or "Jump to definition", you would still get its declaration type, i.e. Optional. With if let, you're redirected to the correct and explicit non-optional definition.

1 Like

This is almost exactly how Kotlin handles optionals (I think it’s called linear typing), and apart from being quite inscrutible (the compiler errs on the side of safety when it can’t be statically proven that the variable won’t be modified from another thread/coroutine, so they example above may not work), in the context of Swift, it would be one more special case regarding optionals to keep in mind.

I think it is important that Swift stays true to its own special cases (of which there are more than a few regarding optionals) without introducing more for the sake of a little additional comfort if the end result would be a language like C++, which takes a lifetime to learn, while losing uniformity or orthogonality.

7 Likes

var task: Task? Is var task: Optional<Task>

The syntactic convenience here is sort of getting in the way. Also the optional promotion rules where an optional will auto wrap an non optional value but not the other way around. Some people call it a subtype behavior where T is a subtype of Optional.

I wonder if you could use wrapper here that give you a default value.

Edit: swift5 - Use Swift @propertyWrapper for dynamic default value? - Stack Overflow