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.

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?

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.

1 Like
Terms of Service

Privacy Policy

Cookie Policy