If not case

What is the recommended way of testing if a value does not correspond to an enumeration case with an associated value?

Given the following declarations:

enum Associated {
    case text(String)
    case number(Float)
    case flag(Bool)
}
let t = Associated.text("my text")

Testing whether a value matches a specific case is readable (though awkward because of the implicit assignment):

if case .text(_) = t {
    print ("found a text")
}

The opposite, testing whether a value does not match a specific case, is more difficult. The straightforward syntax is not accepted by the compiler:

if !(case .text(_) = t) {
    print("not a text")
}

I have found two variants that work: using else leads to quite cryptic code:

if case .text(_) = t {} else {
    print("not a text")
}

and the default of a switch multiplies keywords:

switch n { case .text(_): break default:
    print("not a text") 
}

Some similar questions on the net also suggest a guard, but that is accepted only when followed by return (or break) and thus ignores later statements:

guard case .text(_) = n else {
    print("not a text")
    return 
}

Is there a more readable and elegant way for excluding a case with an associated value?

1 Like

This has come up before.

See Comparing enum cases while ignoring associated values - #5 by Dante-Broggi
and Testing enum cases with associated values - #12 by Andy_Chou

Sadly there is no good solution at the moment that I know of.

1 Like

You can manually define a nested enum without the associated values:

extension Associated {
  enum Case { case text, number, flag }
  
  var `case`: Case {
    switch self {
    case .text: return .text
    case .number: return .number
    case .flag: return .flag
    }
  }
}

This lets you write:

if t.case != .text {
  print("not a text")
}

Is there a more readable and elegant way for excluding a case with an
associated value?

In my experience the best approach depends on the reason why you’re testing for specific values. Once you’ve thought about that, it’s often possible to come up with an abstraction that makes sense in that context.

For example, I’ve hit this problem when trying to associate UI with network state. Given this state:

enum State {
    case initial
    case opening(Connection)
    case open(Connection)
    case closing(Connection)
    case closed
}

how do I know whether to enable my Cancel button? I fix this by adding an isCancellable property that’s only true when cancellation makes sense.

extension State {
    var isCancellable: Bool {
        switch self {
        …
        }
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes