Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift- variable not initialized before use (but it's not used)

Currently I've got some swift code like this:

class C {
   let type: Type;
   var num = 0;
   init() {
       self.type = Type({ (num: Int) -> Void in
           self.num = num;
       });
   }
}

The Swift compiler refuses to permit it, saying that I've referenced self.type before it's initialized, even though that's clearly completely untrue. Furthermore, I can't employ the workaround found in other questions/answers, because the type is not optional, and it's immutable, so it can't be initialized with nil pointlessly first.

How can I make the Swift compiler accept this perfectly valid code?

This has nothing to do with returning from the initializer early. The callback is executed asynchronously- it is stored and then used later.

I also have a few further lets that are initialized after this one. I would have to turn them all into mutable optionals, even though they're not optional and can't be mutated.

like image 757
Puppy Avatar asked Aug 11 '15 13:08

Puppy


People also ask

Do you have to initialize variables in Swift?

All of a class's stored properties—including any properties the class inherits from its superclass—must be assigned an initial value during initialization. Swift defines two kinds of initializers for class types to help ensure all stored properties receive an initial value.

Why is my variable not initialized?

Notice that a variable that is not initialized does not have a defined value, hence it cannot be used until it is assigned such a value. If the variable has been declared but not initialized, we can use an assignment statement to assign it a value.

What is required Initializers in Swift?

An initializer is a special type of function that is used to create an object of a class or struct. In Swift, we use the init() method to create an initializer. For example, class Wall { ... // create an initializer init() { // perform initialization ... } }

Why do we need initialization in Swift?

Swift init() Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.


2 Answers

This works:

class C {
    var type: Type?;
    var num = 0;
    init() {
        self.type = Type({ (num: Int) -> Void in
            self.num = num;
        });
    }
}

I assume you knew that. But you want to know why your version isn't working.

Now for the tricky part: for the line

self.num = num;

to work, the compiler has to pass self to inside the closure. The closure could be and probably is executed inside of the constructor of Type.

This is as if you had written

self.type = Type({ (self: C, num: Int) -> Void in
    self.num = num    
});

which is syntactically wrong but explains what the compiler has to do to compile your code.

To pass this necessary instance of self to the constructor of Type, self has to be initialized. But self isn't initialized, because you are still in the constructor.

The compiler tells you which part of self is not initialized, when you try to pass self to the constructor of Type.

P.S.

obviously Type knows num in your code. If you want to use let in C instead of var you could do...

class Type {
    let num: Int
    init () {
        num = 3
    }
}
class C {
    let type: Type;
    var num = 0;
    init() {
        self.type = Type();
        num = type.num
    }
}

or even

class C {
    let type: Type;
    var num: Int {
        return type.num
    }
    init() {
        self.type = Type();
    }
}

depending on whether you want num to change or not. Both examples compile without error.

like image 153
Gerd Castan Avatar answered Sep 22 '22 08:09

Gerd Castan


First, it's important to explain why this is not perfectly valid code, and that it isn't clear at all that self.type is not used before it is initialized. Consider the following extension of your code:

struct A {
    init(_ f: (Int) -> Void) { f(1) }
}

class C {
    let type: A
    var num = 0 {
        didSet { print(type) }
    }
    init() {
        self.type = A({ (num: Int) -> Void in
            self.num = num
        })
    }
}

If you walk through the logic, you'll note that self.type is accessed via print before it has been initialized. Swift can't currently prove that this won't happen, and so doesn't allow it. (A theoretical Swift compiler might prove that it wouldn't happen for some particular cases, but for most non-trivial code it would likely bump into the halting problem. In any case, the current Swift compiler isn't powerful enough to make this proof, and it's a non-trivial proof to make.)

One solution, though somewhat unsatisfying, is to use implicitly unwrapped optionals:

private(set) var type: A! = nil

Except for the declaration, every other part of the code is the same. You don't have to treat it as optional. In practice, this just turns off the "used before initialization" checks for this variable. It also unfortunately makes it settable inside of the current file, but does make it immutable to everyone else.

This is the technique I've most often used, though often I try to rework the system so that it doesn't require this kind of closure (not always possible, but I often rack my brain to try). It's not beautiful, but it is consistent and bounds the ugly.

Another technique that can work in some cases is laziness:

class C {
    lazy var type: A = {
        A({ (num: Int) -> Void in self.num = num })}()
    var num = 0
    init() {}
}

Sometimes that works, sometimes it doesn't. In your case it might. When it does work, it's pretty nice because it makes the property truly immutable, rather than just publicly immutable, and of course because it doesn't require !.

like image 24
Rob Napier Avatar answered Sep 21 '22 08:09

Rob Napier