"Removing implicit `nil` initial value for Optional-typed variables"

A while ago I read this line of this post by Slava Pestov and it stuck with me.

Today I felt motivated to follow-up on this idea, and I found this thread started by Jordan Rose:

which I see ended with @tera's unanswered question to those with the power to make this decision.

I made this new thread because I'm aware of the existence of some cultural norms about bumping old threads, but I'm not really sure what they are. Otherwise, I might have simply voiced my support in Jordan's thread, since that's really all I'm doing.

I don't currently have any insight to add, other than to strengthen the specific subset of the choir that's saying, "I can't speak to the implementation, but the change to the surface language seems like an obvious improvement to me."

14 Likes

Wholeheartedly agree. Not only is this behaviour weird and surprising, it disrupts the otherwise very useful help you get from the compiler when you e.g. forget to initialise all properties in an init.

struct Person {
    let name: String
    let number: Int?
    var score: Int?

    init(name: String) {
        self.name = name
        // Compiler will remind me to set `self.number` but not `self.score`
    }
}
10 Likes

I also agree with this sentiment. While it is seemingly helpful at times, the "magic" behind this feature also only working with the ?-style syntax is also confusing. For example, this works:

struct SomeStruct {
  let a: Int
  var b: Int?

  init(a: Int) {
    self.a = a
    // b is implicitly set to `nil`...
  }
}

But this doesn't work:

struct SomeStruct {
  let a: Int
  var b: Optional<Int>

  init(a: Int) {
    self.a = a
    // ERROR: Return from initializer without initializing all stored properties
  }
}
6 Likes

I would accept it as a breaking change for Swift 6 and I believe that the pitch is in the correct direction, especially when we’re now focusing on lifetime and ownership👍

8 Likes

I like omitting "= nil" whenever I can, but I agree this is subpar for memberwise initializers. So I would be less opposed to such a change than I was in the past.


The place I'd like the most to keep implicit nil is for @IBOutlet properties:

@IBOutlet var nameField: NSTextField!
@IBOutlet var continueButton: NSButton!

I suppose the @IBOutlet attribute could be an exception that allows implicit nil, mirroring how property wrappers should also continue to be able to provide their own default value.

4 Likes

When this has been discussed in the past it has also been suggested that it might make sense to maintain the behavior for all implicitly unwrapped optionals.

5 Likes

I'll just point out that it's not uncommon to use regular optionals for @IBOutlet too (at least in my code). So I'd rather have it linked to @IBOutlet than whether the optional is implicitly unwrapped or not.

2 Likes

It’s been a very long time since I’ve thought about @IBOutlet. Can you clarify for me (and for anyone else with the same confusion) - what is it about the nature of IBOutlet that would make it more onerous to write out = nil than in all of the other places that this proposed change would affect?

I don't think that having "= nil" would be of any harm in case of IBOutlets:

@IBOutlet var someView: UIView! = nil

So long as IBOutlet variable is properly set to whatever value it needs too be set to (like a view, etc).

It may be perceived as it gets set to nil first, then to a non-nil value, but I don't think that's a problem (whether this double set actually happens or not).

IMHO, implicitly unwrapped optionals could follow the same rules as normal optionals, without being treated specially. In particular, I'd expect this member wise initialiser to be done:

struct S {
    var x: Int!
    var y: Int! = nil
// automatic memberwise  init:
    init(x: Int!, y: Int! = nil) {
        ...
    }
}

While we are on this we could also finally take the opportunity of introducing the real type, "!" is a syntax sugar for:

var x: ImplicitlyUnwrappedOptional<T> // same as:
var x: T!

Agree. This would seem to fit with our treatment of IUOs in general.

We won’t be doing that, since we actively removed it:

7 Likes

In any of the discussions surrounding the recent announcement about Swift 6, has this breaking change been brought up? Any updates about whether or not implicit = nil will be removed?

2 Likes

Somehow the logic here seems the exact opposite of the logic for the shorthand

if let someOptional {

which of course has already been implemented.

I don't see them as very related (and therefore not as opposites, nor in any way contradictory).

if let someOptional { } was previously invalid code, and is now treated as shorthand for if let someOptional = someOptional { }. A rather straight-forward situation.

On the other hand, this code:

/// Code A
struct SomeStruct {
    var optionalProperty: Int?
}

is currently treated as shorthand for:

/// Code B
struct SomeStruct {
    var optionalProperty: Int? = nil
}

If we remove the interpretation of Code A as shorthand for Code B then the Code A is still valid, and importantly it has a rather different meaning than Code B. There is currently no way to express the "original" meaning of Code A, because it is always re-interpreted as shorthand for Code B.

To be clear, the "original" meaning of Code A is something like:

"This is a struct with a single property of type Int? for which an initializer should be synthesized, and that initializer should not provide a default argument for the optionalProperty parameter, and all other initializers must not forget to assign a value of type Int? to optionalProperty."

The meaning of Code B is something like:

"This is a struct with a single property of type Int? for which an initializer should be synthesized, and that initializer should provide the default argument of nil for the optionalProperty parameter, and if any other initializer does not assign a value to optionalProperty then the value nil should be automatically assigned."

3 Likes

Indeed. And while it is possible to "opt-out" of the current unfortunate behaviour by switching to a more explicit Optional<T> form (that doesn't exhibit this drawback!) it is not ideal and not possible at all for implicitly unwrapped optionals, so if you have a struct with an IUO you'd have to implement the struct's memberwise init manually to opt-out of the unwanted behaviour, which could be painful for a struct with many fields. +1 for the change.

2 Likes

Right, I was just in the middle of updating my post to point out that I was wrong. It is actually possible to express the "original" meaning of Code A (as you say, using var optionalProperty: Optional<Int>), which does mean that the situation is not quite as dire as I was painting it.

The current situation is still a bit of a foot-gun though, because it makes it easy to not realize that a property is being implicitly initialized with nil.

I hadn't thought of how property wrappers and attributes such as @IBOutlet (is that implemented as a property wrapper yet?) factor into the situation, and it sounds like they add some additional weight to the argument that we should remove the shorthand.

To be fair to the other side of the debate, this is a breaking change that would probably affect a lot of code, given the ubiquity of optionals... but on the bright side I think the migration would be pretty unambiguous. All var optionalProperty: SomeType? would be re-written as var optionalProperty: SomeType? = nil and no behavior should change, and now everyone is free to delete the = nil from wherever they want.

3 Likes

I thought that part of the thinking on this was that it's better to have explicit initializations rather than implicit initializations. Hence my seeing the opposite thinking with the if let someOptional { } shorthand.

Also, I was mostly thinking about local variables, not ivars, I guess, where the problems you're talking about aren't relevant.

I'm having a hard time thinking of cases where I want optionals to be uninitialized. They either need to be initialized to something or to nil. Same with IUOs. To write sensible code they need to be initialized before first use. IBOutlets are the canonical example of variables initialized after the init method. There are a few tricks you can play using ? with IUOs in some cases but those actually depend on initialization to nil. Adding = nil to every IBoutlet in the known universe doesn't seem like a productive use of anyone's time.

By this logic we should treat "var x: Int" as "var x: Int = 0", "var x: Bool" as "var x: Bool = false", etc. Don't think this reasoning is good. As mentioned adding "= nil" could happen automatically by a migration tool.

2 Likes

I'm not the one proposing a change.
In Obj-C I believe all variables are initialized to 0. All classes are alloced with calloc automaticallly so all variables are zero or nil. In a language that prides itself on safety I don't think that initializing all variables to zero is crazy talk.
Will the compiler still warn/error if a variable isn't initialized in all code paths? I sure hope so.
And regarding the fix-its: You're talking about modifying most of the Swift source code files in the world. Do you think every modification will be carefully inspected? That would be millions of hours of dev time spent on this change.

1 Like
  1. only instance, static and global variables. Local variables are not auto-initialised to 0.
  2. In Obj-C (that is C under the hood) you could still have a struct allocated on stack or with malloc, in which case struct's fields would be garbage.
  3. it is not necessarily a good thing.
enum Enum {
    a = 1,
    b = 2
};
enum Enum x;
int main() {
    printf("%d\n", x); // 0
}

Sure thing.

1 Like

Under ARC, all object-type variables are initialized to nil. And I believe there’s a compiler flag to initialize all locals to 0.

1 Like