Pre-pitch: remove the implicit initialization of Optional variables

I just wrote a thread on this and then realized we could still fix it in a language version.

In Swift 6, optional variables become uninitialized by default, like all other variables. Locals and globals get a fix-it to add = nil. Properties only get that fix-it in a note attached to the error about leaving the variable uninitialized, since it’s not obviously the right thing to do, only the Swift 5 thing to do. A migrator could auto-apply that fix too though.

Thoughts?

See also: What is a 'variable initialization expression'?

52 Likes

I assumed it was done for ? because nobody wants to assign initial values to implicitly unwrapped optional properties (as those values should never be used), and ? came along for the ride with !.

If you can leave it as-is only for !, this is good. You have to leave !s as-is for the UIKit baggage.

2 Likes

You might be interested in the discussion last time I pitched this: Pitch: Remove default initialization of optional bindings

8 Likes

Ugh, I forgot about !. I'd be okay leaving this as magic behavior associated with ! along with its other magic behavior, or with tying it to @IBOutlet instead of the optionality of the property.

5 Likes

This was also my recollection too — implicitly unwrapped optionals need to keep this IMO.

2 Likes

I'm 100% behind this. It's causing a lot more damage than it's helping improve language ergonomics. And a side effect of this is the mental model for auto-synthesized inits being all over the place. It's possibly my number one pet peeve in the language at the moment.

11 Likes

Property wrappers also allow implicit initialization.

@propertyWrapper
struct Wrapper<T: FloatingPoint> {
  var wrappedValue: T
  init(wrappedValue: T = .nan) {
    self.wrappedValue = wrappedValue
  }
}

struct Test {
  // Uses default argument.
  @Wrapper var x: Float64
}

var test = Test()
test.x.isNaN  //-> true
5 Likes
  • i like that var v: Int? in classes is auto initialised to nil - it reduces the visual noise and makes it compatible with var v: Int! case.

  • i like that var v: Int? as a local / global variable is auto initialised to nil - same reasons as above

  • and i really hate this, as it is actively harmful:

    struct S {
        var a: Int
        var b: Int?
        var c: Int? = nil
    }
    let x = S(a: 0, b: nil, c: nil) // ok
    let z = S(a: 0, b: nil) // ok
    let y = S(a: 0, c: nil) // *** HHHMMM???
    let w = S(a: 0) // *** HHHMMM???

in order to "opt out" of argument default value i need to change Int? to Optional<Int> - and that's not obvious at all, as the Int? feels just a short hand syntax for Optional<Int> (same way as [Int:Int] is a short hand syntax to Dictionary<Int, Int>)

i believe though that this latter problem can be fixed independently, leaving the current auto initialising behaviour in place. ditto the other mentioned issue of variables being initialised twice.

6 Likes

I'm prolly in favour of this but I expect it would get a lot of pushback from the wider developer community as being 'pedantic', 'visual noise'. The issues that implicit initialisation brings aren't very clear unless you get into the weeds of the Swift initialisation rules, etcetera.

4 Likes

+1 - I was really puzzled about those 'variable initialization expression' things, and I don't think I would have ever discovered that this was the cause had I not asked. So thanks again to @lukasa and everyone else.

I'm happy my issue was able to be resolved, but I don't think it's a good situation for future developers who encounter this. It's hard to write fast, efficient code, and IMO the language should do its best to support you with that; not make it harder.

I think it's also important to remember that not only would this change make the language model simpler, but also the performance improvements and binary size reductions that I observed with my project could also apply to other applications.

I'll post some numbers later, but I'd also encourage others to profile their code, look for 'variable intialization expression' to identify problem areas, and try it out on their own code.


I actually really don't like implicit initialization for locals. Personally, I'm a big fan of Swift's definite initialization feature, which I often use with let variables:

let x: SomeType // not initialized

if someCondition {
  x = ...
} else {
  x = ...
}

foo(x) // Will not compile if 'x' is not initialized along all code paths.

But this language feature gets broken by implicit initialization - if SomeType is an optional (written with sugar, because that's the idiomatic/most obvious way to write it), x will implicitly be set to nil, meaning it now needs to become a var so that I can actually initialize it.

It's a minor issue compared to the inlining/performance problems, but still. It bugs me.

15 Likes

Eliminating implicit initializations would lead to behavioral alignment, which is what we need for the design of Swift. With major version bump, we’re allowed to displease some developers — because it would make a lot more newcomers less confused.

+1 for this (with Swift 6)

4 Likes

Thankfully, they changed how optional let bindings work a while ago so that they don't implicitly initialize to nil. Your code now works for optional no problem.

4 Likes

+1 I don't think requiring the addition of = nil is a burden to the writer and it makes the intent clearer to a maintainer/reader.

2 Likes

Same, It should work like any other initialization

2 Likes

"variable initialization expression" / double initialisation has nothing to do with implicit initialisation of T? variables... these are two completely separate issues!

example:

struct S {
    let int: Int
    init(_ int: Int) {
        print("S init with \(int)")
        self.int = int
    }
}

struct MyStruct {
    var s: S = S(1)
    init(s: S) {
        print("setting s to \(s.int)")
        self.s = s
        print()
    }
}

func foo(_ s: S) {
    print("foo start")
    let s = MyStruct(s: s)
}

Output:

foo start
S init with 1 // ** BUG **
setting s to 2

here that the "s" is initialised twice is the bug, and it has nothing to do with implicit initialisation of T? variables. the latter is just a manifestation of this bug in var v: T? case... but the same bug will happen in the var v: T? = nil case.

Double initialization is not a bug, it’s a feature. :-) That is, the behavior you’re describing was deliberate, if not always what people want. The problem is for Optional variables there’s no way to turn it off, which usually doesn’t matter but sometimes does.

2 Likes

The acceptance notes for SE–0242 might suggest otherwise:

2 Likes

I think we should get rid of the implicit initialisation of optional values. Making the language more consistent overall is more valuable than saving a few chars IMO. Aside from consistency, this "forced initialisation" thing:

var thing: Int?

if thePlanetsAreAligned() {
    thing = 4
}

// And if they are not aligned, what then? In cases where `thing` is _not_ optional I really value the compiler error Swift throws here.

is to me the most compelling reason to change it.

I don't really understand why ! should be treated any differently. In this thread it has been brought up as an "oh damn! yeah.. that ol' chestnut." case. But to me it should be treated exactly the same: var myView: UIView! = nil – isn't that just reflecting reality?

The consensus in the older thread @Slava_Pestov linked to seemed to be "Yeah, we're using that but we wouldn't be mad if it was gone", which is about the same as I feel about it. I know about it and I use it because I can, but I'm more for consistency than brevity (which seems to fit Swift's overall philosophy too).

I agree with this sentiment. It also helps demystify IUO's for newcomers by removing one layer of (IMO) excessive "magic".

5 Likes

The problem with an argument from "consistency" is that it's circular. The argument is that it's good to be consistent, because consistency is good.

Why is consistency good?

(I'm not actually inviting answers to that unanswerable question, but rather pointing out that there's no answer that doesn't take the use-case into account.)

Also, I don't think the argument for omitting the initializer is brevity, but rather in eliminating redundancy. Why tell the compiler something that it already knows? By far the most common use case is = nil, because if you know the value from the start you likely don't need an optional. (The opposite case, where you start with a non-nil value but need to be able to set a nil value later exists, of course, but it doesn't seem typical to me.)

The other un-addressed issue here (well, it was sort of addressed at the end of this post) is that the declaration of an optional has a unique syntactic form (because of the ? after the type). That means it should be easy for the compiler to recognize the difference between this and a non-optional declaration, so nothing is forcing the compiler to add a boilerplate = nil initializer (along with the performance implications that has) to the optional form just to make it consistent (hmm, see above) with the non-optional form.

IOW, the compiler could set the storage to nil cheaply without an initializer expression, if someone chose to put that behavior into the compiler.

The only downside here (and the only inconsistency that matters, I think) is the ambiguity of this local variable construct:

var myOptional: Int?
if (myBool) {
    myOptional = 4
}

Should this produce an error (that myOptional isn't initialized on all paths) or not? It's unclear which choice would be the most beneficial.

5 Likes