i always held the opposite opinion, that having a none case in an enum was a smell, and indicated that things that hold the enum should be holding an optional of the enum instead.
It's similar to having an Optional inside an Optional, in that there are some cases where that makes sense semantically. But, it's uncommon so indeed it's always wise to ask yourself if you really need it.
But there are simple expressivity benefits to an explicit none case as opposed to using optionality. You can distinguish between "unspecified" and "none" for example, which are sometimes different things.
There’s nothing wrong with an enumeration holding another enumeration. An optional indicates that there is either a wrapped value or no value. If you want to express either a wrapped enumeration or no value, then an optional enumeration is a perfectly fine way to express that.
Can you show an example of a developer recommending avoiding this? I’m interested in seeing their reasoning.
Personally, I’ve found that nested optionals are actually something to avoid. The concept of “either a wrapped value, or no value, or no value” doesn’t really make sense. Often, when a double optional is used, there is a more expressive alternative (for example, an enumeration with a “wrapped value” case, a “no value” case, and a “value not yet calculated” case).
Additionally, many of Swift’s features interact awkwardly with nested optionals. For example:
let doubleOptional0: Int?? = Optional(nil)
print(doubleOptional0 as Any) // prints nil
let optional1: Int? = nil
let doubleOptional1: Int?? = Optional(optional1)
print(doubleOptional1 as Any) // prints Optional(nil)
This aversion to nested optionals isn’t limited to me, either: there are many features in Swift that are designed to avoid nesting optionals by flattening them, such as optional chaining, as? casting, the nil-coalescing operator ??, and the try? operator.
I would prefer Wrapped + Optional if Wrapped is used on it's own, and Wrapped with extra case, if otherwise Wrapped would always be used inside Optional.
And sometimes it is helpful to have both:
enum ValidUseCase {
case a
case b
}
enum UseCase {
case a
case b
case invalid
init(valid: ValidUseCase) {
switch valid {
case .a: self = .a
case .b: self = .b
}
}
var asValid: ValidUseCase? {
switch self {
case .a: return .some(.a)
case .b: return .some(.b)
case .invalid: return .none
}
}
}