Coder-256
Coder-256

Reputation: 5618

Swift - Take Nil as Argument in Generic Function with Optional Argument

I am trying to create a generic function that can take an optional argument. Here's what I have so far:

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // Errors!

It works with a String as shown, but not with nil. Using it with nil gives the following two errors:

error: cannot invoke 'somethingGeneric' with an argument list of type '(_?)'
note: expected an argument list of type '(T?)'

What am I doing wrong and how should I correctly declare/use this function? Also, I want to keep the usage of the function as simple as possible (I don't want do something like nil as String?).

Upvotes: 20

Views: 12682

Answers (7)

om-ha
om-ha

Reputation: 3582

Actually there is a way to do this, inspired by Alamofire's internal code.

You do not have to install Alamofire to use this solution.

Usage

Your problematic method definition

func someMethod<SomeGenericOptionalCodableType: Codable>(with someParam: SomeGenericOptionalCodableType? = nil) {
        // your awesome code goes here
}

What works ✅

// invoke `someMethod` correctly 
let someGoodParam1 = Alamofire.Empty.value
someMethod(with: someGoodParam1)

I think it is possible to use Alamofire.Empty.value as a default value in someMethod definition as a parameter.

What does not work ❌

// invoke `someMethod` incorrectly
let someBadParam1: Codable? = nil
let someBadParam2 = nil
someMethod(with: someBadParam1)
someMethod(with: someBadParam2)

Solution definition (source)

/// Type representing an empty value. Use `Empty.value` to get the static instance.
public struct Empty: Codable {
    /// Static `Empty` instance used for all `Empty` responses.
    public static let value = Empty()
}

Upvotes: 2

Rob Napier
Rob Napier

Reputation: 299345

I believe you've overcomplicated the problem by requiring the ability to pass untyped nil (which doesn't really exist; even nil has a type). While the approach in your answer seems to work, it allows for the creation of ?? types due to Optional promotion. You often get lucky and that works, but I've seen it blow up in really frustrating ways and the wrong function is called. The problem is that String can be implicitly promoted to String? and String? can be implicitly promoted to String??. When ?? shows up implicitly, confusion almost always follows.

As MartinR points out, your approach is not very intuitive about which version gets called. UnsafePointer is also NilLiteralConvertible. So it's tricky to reason about which function will be called. "Tricky to reason about" makes it a likely source of confusing bugs.

The only time your problem exists is when you pass a literal nil. As @Valentin notes, if you pass a variable that happens to be nil, there is no issue; you don't need a special case. Why force the caller to pass an untyped nil? Just have the caller pass nothing.

I'm assuming that somethingGeneric does something actually interesting in the case that it is passed nil. If that's not the case; if the code you're showing is indicative of the real function (i.e. everything is wrapping in an if (input != nil) check), then this is a non-issue. Just don't call somethingGeneric(nil); it's a provable no-op. Just delete the line of code. But I'll assume there's some "other work."

func somethingGeneric<T>(input: T?) {
    somethingGeneric() // Call the base form
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric() {
   // Things you do either way
}

somethingGeneric(input: "Hello, World!") // Hello, World!
somethingGeneric() // Nothing

Upvotes: 9

Dylan Reich
Dylan Reich

Reputation: 1430

Here is the solution I came up with that compiles on Swift 5, as many of the solutions here did not compile for me. It might be considered hacky as I use a stored variable to help things along. I was unable to come up with a Swift 5 version of the nil parameters that resolve to type T.

class MyClass {
    func somethingGeneric<T>(input: T?) {
        if let input = input {
            print(input)
        }
    }

    func somethingGeneric() {
        somethingGeneric(Object.Nil)
    }

}

final class Object {
    static var Nil: Object? //this should never be set
}

Upvotes: 1

stonecanyon
stonecanyon

Reputation: 119

Good question and answer. I have an Swift 4 update to contribute:

var str: String? = "Hello, playground"
var list: Array<String>? = ["Hello", "Coder256"]

func somethingGeneric<T>(_ input: T?) {
  if (input != nil) {
    print(input!);
  }
}

func somethingGeneric(_ input: ExpressibleByNilLiteral?) {}


somethingGeneric("Hello, World!")    // Hello, World!
somethingGeneric(nil)                // *nothing printed*
somethingGeneric(nil as String?)     // *nothing printed*
somethingGeneric(str)                // Hello, playground
str = nil
somethingGeneric(str)                // *nothing printed*
somethingGeneric(list)               // ["Hello", "Coder256"]
list = nil
somethingGeneric(list)               // *nothing printed*

Upvotes: 5

Valentin
Valentin

Reputation: 11802

I think that you will never call somethingGeneric(nil) but mostly somethingGeneric(value) or somethingGeneric(function()) for which the compiler has enough info not to be stucked trying to guess the type:

func somethingGeneric<T>(input: T?) {
    if let input = input {
        print(input);
    }
}

func neverString() -> String? {
    return nil
}

let a: String? = nil

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(a) // Nothing and no error
somethingGeneric(neverString()) // Nothing and no error

Also, I would use the if let syntax instead of if(value != nil).

Upvotes: 2

Coder-256
Coder-256

Reputation: 5618

I figured it out:

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric(input: NilLiteralConvertible?) {}


somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // *nothing printed*
somethingGeneric(nil as String?) // *nothing printed*

Upvotes: 2

Mike Pollard
Mike Pollard

Reputation: 10195

I guess the compiler can't figure out what T is just from nil.

The following works just fine though for example:

somethingGeneric(Optional<String>.None)

Upvotes: 13

Related Questions