How to check if a value of a generic type is nil?

Hi everyone :slight_smile:

Basically I want to do something like this:

func foo<T>(bar: T) {
  if bar == nil {
    // do something
  } else {
    // do something else
  }
}

With this code, the compiler gives me following warning: Comparing non-optional value of type 'Value' to 'nil' always returns false.
The method should handle Optional and Non-Optional Types and I don't really want to write another overloaded method to distinguish between Optional and non optional types.

I tried several things like conditionally casting to an Optional, but without success.

Is there any possibility I can solve this problem? Any help or comment is appreciated.

1 Like

Creating a custom protocol like this:

protocol OptionalValue {
    var isNil: Bool { get }
}

extension Optional: OptionalValue {
    var isNil: Bool {
        return self == nil
    }
}

and then use it in the function use it like this:

func foo<T>(bar: T) {
  if let optionalBar = bar as? OptionalValue, optionalValue.isNil {
    // do something
  } else {
    // do something else
  }
}

works. It seems a little bit too complex for such a simple problem to be honest. Is there any easier solution?

1 Like

I find it unclear what you mean by this.

I think (?) what you mean is that the method handles both nil and non-nil values - in other words, just regular optionals:

func foo<T>(bar: T?) {
   guard let barValue = bar else {
     // 'bar' is nil.
     return
   }
   // barValue is available with type 'T'.
}

foo(bar: "hello, world") // works. T == String.

Does that help you? Or do you mean something else?

2 Likes

Thanks Karl, this seems to work and it's exactly what I wanted.

Seems like the compiler is smart enough in this case and T is never inferred to Optional<...>. I didn't know that.
Tho this will only work for generic methods and not for methods in generic classes where the compiler has to infer T earlier.
In the case, that T is Optional<String>, guard let barValue = bar will never fail because bar has the value .some(.none) when its nil.

Can you give an example of the issue you're seeing with classes? I don't see why the following wouldn't work:

class MyClass<T> {
  var someData: T?

  init(someData: T?) {
    self.someData = someData
  }
}

let test = MyClass(someData: "hello") // test is MyClass<String>

The key thing is that you're not parameterised over the optional type itself, but rather the type of data within the optional. To illustrate - imagine instead of being an optional, someData was an Array<T> (or [T], to use the shorthand form), and you used someData.isEmpty as a way of signalling nil. Going with something like OptionalValue would be analogous to that type being generic over the collection itself, not just the data within it.

I don't know if it helps to look at things that way, but it makes sense to me, so I figured I'd share it.

This is a simple example which is not working as I would like it to work (please ignore that the code is pointless):


class Foo<T> {
    let value: T

    init(value: T) {
        self.value = value
    }

    func foo(bar: T?) {
        if let barValue = bar {
            print("bar is not nil \(barValue)")
        } else {
            print("bar is nil")
        }
    }
}

let value1: String = "foo"
let foo1 = Foo(value: value1)
foo1.foo(bar: value1) // prints: bar is not nil foo
let value2: String? = nil
let foo2 = Foo(value: value2)
foo2.foo(bar: value2) // prints: bar is not nil nil

The problem here is, that the type of the data within the optional is also an Optional.

It might also be helpful to be a bit more specific about why you are trying to distinguish between optionals and non-optionals. If a function/type is generic over a value of (unconstrained) type T, then from an internal perspective your implementation probably shouldn't care about whether T happens to be an optional type or not. Special-casing behavior when T is an optional will likely result in unexpected behavior for clients who expect the implementation to be agnostic with respect to the actual identity of the generic parameter.

My use case is pretty simple, I want a wrapper around UserDefaults which also supports Optionals.
If the value == nil -> remove the stored value for the key
If the value != nil -> store the value for the key

It seems like UserDefaults is not happy when I try to call the set(value, for: key) method with nil.

1 Like

Ah, got it. Unless I'm missing something, it seems like this issue would be more general that just OptionalUserDefaults only supports a closed set of types, and trying to use any type other than the blessed types will result in a crash. E.g., this also results in a SIGABRT:

class C {}
UserDefaults.standard.set(C(), forKey: "key")

I'm not sure exactly what the design of your UserDefaults wrapper looks like, but I think you probably want to define a protocol to represent "types which can be stored into UserDefaults," something like this:

protocol UserDefaultsStorable {
  func store(into defaults: UserDefaults, under key: String)
}

extension Int: UserDefaultsStorable { ... }
extension Double: UserDefaultsStorable { ... }
...

class UserDefaultsWrapper<T: UserDefaultsStorable> {
  ...
}

You could also leave UserDefaultsStorable as a no-requirement protocol to indicate "types that will work as-is with UserDefaults.set(_:forKey:)," but the main idea here is to make sure that your wrapper can only work with types that someone has endorsed as "able to be stored in UserDefaults", which notably does not include Optional.

2 Likes

This should work:

func foo<T>(bar: T) {
    if (bar as AnyObject) is NSNull {
        print("it is nil")
    } else {
        print("it is not nil")
    }
}

I was also looking for this answer in context of a wrapper for UserDefaults and found the answer here: How to check if a Generic type is nil in Swift - Stack Overflow

2 Likes

I recently answered this elsewhere on SO; NSNull is not necessary.

1 Like

Make function foo itself generic?

class Foo<T> {
    let value: T

    init(value: T) {
        self.value = value
    }

    func foo <U> (bar: U?) {
        if let barValue = bar {
            print("bar is not nil \(barValue)")
        } else {
            print("bar is nil")
        }
    }
}

let value1: String = "foo"
let foo1 = Foo (value: value1)
foo1.foo (bar: value1)
let value2: String? = nil
let foo2 = Foo (value: value2)
foo2.foo (bar: value2)

prints

bar is not nil foo
bar is nil

Has this changed? The following compiles fine for me:

UserDefaults.standard.set(nil, forKey: "key") // compiles fine

This is how it is declared in UserDefaults:

/**
-setObject:forKey: immediately stores a value (or removes the value if nil is passed as the value) for the provided key in the search list entry for the receiver's suite name in the current user and any host, then asynchronously stores the value persistently, where it is made available to other processes.
*/

open func set(_ value: Any?, forKey defaultName: String)

Interestingly there's another type specific set:

open func set(_ url: URL?, forKey defaultName: String)

No idea why it is specialised.

It compiles, but in case if the value is generic it will crash in runtime:

func store<T>(bar: T) {
    
    UserDefaults.standard.set(bar, forKey: "key")
}

var value: Date? = Date()
store(bar: value) // works

value = nil
store(bar: value) // crash!

Why that's better than the ordinary:

UserDefaults.standard.set(nil, forKey: "key")

that works totally fine?

And if to use a wrapper (for whatever the reason is), perhaps it should be typed with optional:

func store<T>(bar: T?) { ... }
1 Like

You can use polymorphism:


func foo<T>(value: T) {
    // do something with value
}

func foo<T>(value: Optional<T>)  {
    if let value {
        foo(value: value)
    } else {
        // value is nil
    }
}
3 Likes

I would do what Dictionary already does: Just always take a T?.

If the user has a non-optional they want to store, Swift will implicitly promote it to an optional for them, anyway.

1 Like