Swift optional inout parameters and nil

Is it possible to have an Optional inout parameter to a function in Swift? I am trying to do this:

func testFunc( inout optionalParam: MyClass? ) {
    if optionalParam {
        ...
    }
}

...but when I try to call it and pass nil, it is giving me a strange compile error:

Type 'inout MyClass?' does not conform to protocol 'NilLiteralConvertible'

I don't see why my class should have to conform to some special protocol when it's already declared as an optional.


Solution 1:

It won't compile because the function expecting a reference but you passed nil. The problem have nothing to do with optional.

By declaring parameter with inout means that you will assign some value to it inside the function body. How can it assign value to nil?

You need to call it like

var a : MyClass? = nil
testFunc(&a) // value of a can be changed inside the function

If you know C++, this is C++ version of your code without optional

struct MyClass {};    
void testFunc(MyClass &p) {}
int main () { testFunc(nullptr); }

and you have this error message

main.cpp:6:6: note: candidate function not viable: no known conversion from 'nullptr_t' to 'MyClass &' for 1st argument

which is kind of equivalent to the on you got (but easier to understand)

Solution 2:

Actually what @devios1 needs is "optional pointer". But inout MyClass? means "pointer to an optional". The following should work in Swift 4

class MyClass {
    // func foo() {}
}

func testFunc(_ optionalParam: UnsafeMutablePointer<MyClass>? ) {
    if let optionalParam = optionalParam {
        // optionalParam.pointee.foo()
        // optionalParam.pointee = MyClass()
    }
}

testFunc(nil)

var myClass = MyClass()
testFunc(&myClass)

Solution 3:

This is possible, and it may have everything to do with Closure.()

The optional can have a nil value, but it is not expecting nil. So the optional would work if you simply did not pass it to the function or passed a variable of type MyClass? with value nil. Like Bryan states.

When you write the function it has to have a default value to be an optional param like so:

func testFunc(inout optionalParam:MyClass?={var nilRef:MyClass?;return &nilRef}()) {
    if optionalParam != nil {
        ...
    }
}

Notice if optionalParam {...} is changed to if optionalParam != nil {...}

you can NOT uwwrap optionalParam without checking it,i.e.if optionalParam? != nil, as uwrapping, optionalParam? fails when optionalParam==nil. ~note, var a :MyClass? has a value of nil until it is assigned.

Call is either, testFunc(optionalParam:&a) or testFunc(), but never testFunc(nil)

Think you may be getting two separate concepts intertwined as they have similar names, optional parameters and optionals. Optionals are variables with an extended nil condition. Optional parameters are function parameters which are optional.

Further reading here. Apple is trying to change the verbiage from optional parameter to 'parameters with default values'. It is unfortunate they didn't incorporate optional parameter behavior within these new optional thingies. What is the point of passing nil optionals as optionals if they will fail when unwrapped. Maybe it gets deeper at the heart of what this optional thing is if it can be passed before unwrapping... Schrodinger's cat

Solution 4:

The exact passage from the docs is:

You can only pass a variable as the argument for an in-out parameter. You cannot pass a constant or a literal value as the argument, because constants and literals cannot be modified. You place an ampersand (&) directly before a variable’s name when you pass it as an argument to an inout parameter, to indicate that it can be modified by the function.

Note however, that (edited) per @gnasher's comment, you only need to pass a class as inout (aka by reference), if you intend or want to allow for the instance to be / being replaced by another instance, and not just have the original modified. The passage in the documentation that covers this is:

In-Out Parameters

Variable parameters, as described above, can only be changed within the function itself. If you want a function to modify a parameter’s value, and you want those changes to persist after the function call has ended, define that parameter as an in-out parameter instead.

Here are three tests that cover the usage of var and inout.

class Bar : Printable {
    var value = 1

    init(_ value:Int) { self.value = value }
    var description:String { return "Bar is: \(value)" }
}

let bar = Bar(1)
func changeBarWithoutInoutSinceBarIsAClassYaySwift(b:Bar) { b.value = 2 }
changeBarWithoutInoutSinceBarIsAClassYaySwift(bar)
println("after: \(bar)") // 2

var bar2 = Bar(0)
func tryToReplaceLocalBarWithoutInout(var b:Bar) { b = Bar(99) }
tryToReplaceLocalBarWithoutInout(bar2)
println("after: \(bar2)") // 0

var bar3 = Bar(0)
func replaceClassInstanceViaInout(inout b:Bar) { b = Bar(99) }
replaceClassInstanceViaInout(&bar3)
println("after: \(bar3)") // 99