Encountering a bug with Swift 5.5:
func isNil<T>(_ value: T) -> Bool {
value == nil
}
Always returns false even when nil
is passed in.
Please let me know if this is a known bug or if I should file a report.
Thanks
Encountering a bug with Swift 5.5:
func isNil<T>(_ value: T) -> Bool {
value == nil
}
Always returns false even when nil
is passed in.
Please let me know if this is a known bug or if I should file a report.
Thanks
Seems expected to me. If you pass a nil optional, say Optional<String>
, your comparison becomes Optional<String>.none == Optional<Optional<String>>.none
. Clearly this is never true. If you want to handle optionals you need a separate method which takes T?
.
Not a bug. You're comparing a value of type T
to Optional<T>.none
. If you pass in a value of nil
, then T
is an optional type (that is, Optional<U>
, for some type U
), and Optional<U>.none
is not equal to Optional<T>.none
, aka Optional<Optional<U>>.none
.
Ok
That doesn't work either. It never gets called because we're calling it from another function which itself has a generic type property, which is unavoidable because it's a property wrapper's accessor:
@propertyWrapper struct Foo<Bar> {
var wrappedValue: Bar { ... }
}
Why does Swift make it impossible to simply check if some particular variable contains nil
?
I think this seems brokenly idiotic and should be fixed. We should not have to make two implementations of everything just because Swift refuses to check whether a variable is equal to nil unless it statically knows the type is optional.
The only decent workaround I've found is to do this:
if "\(value)" == "nil" { ... }
But that's an ugly hack and the language should not force us into ugly hacks to avoid odious code duplication.
No, it doesn't. The comparison remains .none == nil
no matter how many layers of optional nesting exist in the type.
Comparing the same exact value to nil outside the context of a generic function always works fine. Based on Liksov's Substitution Principle then it should also work fine inside the generic context.
The only other sane solution would be for optionals to not be allowed to be passed into generic params that have a different level of optionality than the thing being passed in, which clearly we're not prepared to do.
Another problem is that Swift insists on type-erasing the optional wrapping and then not allowing a cast back to an optional or dynamic or a static dispatch to a function with an optional generic parameter to unerase the optionality (we did try dynamic function / subscript to no avail).
Even worse if you end up with an array of type Array<T>
, you can't even do a compactmap to get rid of the nil
values in the array, and when you get elements out of it, you cannot do a simple guard let
to check dynamically for a nil
where you know one might exist.
I would argue we should have a way to dynamically check for nil
in contexts where the static optionality has been lost.
That’s no different than saying I should be able to check value == “string”
no matter the context. Optional
is just a type in Swift, nothing special. The only special thing about it are the affordances the language has to make it easier to work with.
You're arguing like a language-lawyer and not a language user.
value == "string"
can be done as (value as? String) == "string"
, and that will fail as expected if value
isn't a String.
There is no equivalent for testing possible Optionals because nil
isn't any specific type. i.e. writing (value as? Optional<String>) == nil
could fail because value
isn't nil
or because it's not a String?
. But you can't even write that in a generic context, because what do you put as the RHS of that comparison?
A strange response. Optional
still isn’t special here. This cast isn’t possible with most generic types. value == []
isn’t possible either. I’m not sure what the feature would be called that you’d need. Partial type casting? Something like (value as?Optional<_>) == Optional<_>.none
or something like that.
Even as a language user, "checking for nil" is somewhat ambiguous because optionals can be nested.
The simple case is plain optionals, such as Optional<String>
aka String?
: it is nil, or not.
But consider Optional<Optional<String>>
aka String??
: it can contain a string .some(.some("foo"))
, or two kinds of nil: .none
and .some(.none)
.
Add more levels of optionality, and you have even more "kinds of nil".
The game here is to understand what the OP is after, which "kind of nil" has to be checked for.
Is it only dealing with plain single-level optionals? In this case, there is no need for a isNil
function: value == nil
is just enough, and there's not much any question.
Since there is a question, I guess we're talking about "flattening nested optionals", or "deep unwrapping of optionals". There are multiple threads about this topic in this forum, and one I can remember is Challenge: Flattening nested optionals.
And if this guess is wrong, then I advise @vitamin to tell what is, exactly, the initial problem to solve. Focusing on the isNil
function, much likely a by-product on the way to solve the initial problem, is probably a loss of time for everybody.
This is not a bug and the compiler also rightfully warns you about it: "Comparing non-optional value of type 'T' to 'nil' always returns false"
That's a very unhelpful response. The OT already knows about this. It's literally what the post is about. The problem is that T
could be Optional
, which means it should be comparable to nil
. The compiler-acceptable method for dealing with this is completely non-obvious.
I don't think this framing helps the conversation much either. T
certainly could be Optional
. It could also be String
, or Int
, or ByteBuffer
, or SelectableChannel
, or LazyVStack
, or any of the other infinity of types in the program. Should a generic type be comparable to arbitrary values of other types? I think in general the Swift community agrees that the answer is no.
The problem is that you cannot cast to Optional
, because that is an insufficiently specified type. Much like Go, Swift has typed nil. nil
looks like a single unitary value, but it is not: there is one nil
per possible type, an infinity of nil
s.
In this context, what does it mean to compare a value of type T
to nil
? Which nil? Swift is only aware of one nil
in this context, which is the nil
for Optional<T>
, so that's what it assumes this means. It performs implicit optional promotion to the left hand side of OPs expression and so compares Optional<T>.some(value)
to Optional<T>.none
and concludes they are different.
I think it's fair to summarise the OP's ask as: "I would like a way to know an unconstrained generic type T is of the form Optional<U>
, without having to only accept types of the form Optional<U>
." This is totally possible: it's just code, just engineering. But it's not possible without a change to Swift.
Because we cannot know ahead of time what values this function will be called with, we'd need to use the only information we have: the value witness table for T
. This would essentially need an entry (that does not exist in Swift today) that provides a way to load the representation for Optional<T>.none
at runtime. This function pointer can be NULL for types which are not Optional
, and so we can clearly distinguish the difference.
Swift could totally have done this! But it did not, and it would probably be ABI breaking to do it now, and in my personal opinion the utility it adds is fairly minimal.
This is, of course, possible.
protocol OptionalProtocol {
var isSingleLevelNil: Bool { get }
var isDeepNil: Bool { get }
}
extension Optional: OptionalProtocol {
var isSingleLevelNil: Bool {
self == nil
}
var isDeepNil: Bool {
switch self {
case .none:
return true
case .some(let value as OptionalProtocol):
return value.isDeepNil
case .some(_):
return false
}
}
}
let singleLevelOptional: Optional<Int> = 42
print(singleLevelOptional.isSingleLevelNil) // false
print(singleLevelOptional.isDeepNil) // false
let singleLevelOptional2: Optional<Int> = nil
print(singleLevelOptional2.isSingleLevelNil) // true
print(singleLevelOptional2.isDeepNil) // true
let nestedOptional: Optional<Optional<Optional<Optional<Int>>>> = 42
print(nestedOptional.isSingleLevelNil) // false
print(nestedOptional.isDeepNil) // false
let nestedOptional2: Optional<Optional<Optional<Optional<Int>>>> = nil
print(nestedOptional2.isSingleLevelNil) // true - this is .none(.none(.none(....)))
print(nestedOptional2.isDeepNil) // true
let nestedOptional3: Optional<Optional<Optional<Optional<Int>>>> = .some(.some(.some(.none)))
print(nestedOptional3.isSingleLevelNil) // false
print(nestedOptional3.isDeepNil) // true
If you have an unconstrained type T
, you can downcast to OptionalProtocol
and check isSingleLevelNil
or isDeepNil
, depending on the precise semantics you're looking for.
You need a protocol because plain Optional
(with its generic parameters left unspecified) is not a type you can instantiate. The same applies to Array
and Dictionary
; AFAIK the only time you can use a generic type without specifying its parameters is when declaring protocol conformances. At all other times, you have to specify the parameters (Array<Int>
, Array<T>
, or Dictionary<String, T.SomeAssociatedType>
).
If you want to define a common interface or add some members to all Optional
s (or all Arrays, Dictionaries, etc), use a protocol.
It would be cool if we had some sort of generics existential - something like an Optional<?>
or Array<?>
, but you can work around it pretty easily as demonstrated above. It would basically work like a compiler-generated protocol.
I use this trick in my own code. My sympathy with the OP lies in the fact that this solution is a very non-obvious replacement for x == nil
.
Sure, but I don't think that's a problem with optionals - that's just generics in general. The same applies to nested arrays, dictionaries, etc.
Writing generic code requires you to think more abstractly than writing code which uses concrete types. Generally, it's good to add constraints at as high a level as possible to define the interfaces your code requires, but if you can't do that, you'll have to try casting to an appropriate interface.
(apologies if this was already explained upthread and I missed it)
It might help to understand why this compiles and why it's false, which is a lot of what is causing the confusion. Ideally it wouldn't compile at all because T
is not optional. But it does, because we do implicit optional conversion. This code is actually:
func isNil<T>(_ value: T) -> Bool {
let tmp = Optional<T>.some(value)
return tmp == nil
}
You can never compare an arbitary T
to nil
because it isn't optional (just like you can't compare to T
to "foo"
) but the compiler implicitly wraps the value in an optional for you, and then it works. In general implicit optional promotion is essential to the smooth operation of the language, but it does lead to some pretty unfortunate bear traps like this.
This is closely related to Generic types as generic constraints.
Indeed, if every generic type had an implicit protocol that abstracted all the capabilities of the type (or at least if things behaved as though there were such a protocol), then the idea proposed in that thread would work directly, without any extra complexity.
You could write, eg, extension Dictionary where Value: Optional
, and it would work as expected.
And for this thread, once SE–03039: Unlock existentials for all protocols lands, then implicit protocols for generic types would make it possible to write this:
extension Optional {
var isNil: Bool { self == nil }
}
func isNil<T>(_ value: T) -> Bool {
return (value as? Optional)?.isNil ?? false
}
Or even just this, without extending Optional
at all:
func isNil<T>(_ value: T) -> Bool {
guard let x = value as? Optional else { return false }
return x == nil
}
(That ==
is the _OptionalNilComparisonType
version, because there is no Equatable
constraint.)
I wish this was the case.
I wish Swift had less of these magic convenience features/exceptions. If implicit optional conversion had not existed and if we used .none
instead of nil, then the code wouldn’t compile and the programmer would more clearly see a more consistent set of fewer rules.
Out of curiosity and at the risk of getting off topic: What are some examples of how they are essential? Maybe there are threads discussing this, ie whether conveniences that introduces inconsistencies/confusion is worth adding to the language or not?
Without optional promotion you would have to wrap a lot of things in optionals. It would happen constantly and be intensely annoying:
takesOptionalArgument(.some(x))
myDictionary[key] = .some(x)
let x: Int? = .some(1)
if x == .some("foo") {
This is essential sugar, there's no question in my mind it's needed. The question is, can that sugar be narrowed slightly so the above continues to work, but this case did not. It's a tricky needle to thread.
This is conflating two things I think. nil
and .none
are synonyms.
And do be fair the code in the OP does produce a warning:
comparing non-optional value of type 'T' to 'nil' always returns false
This thread is really more about wanting different semantics from the language, so even once the reason it's false are understood, the desire for it to behave differently remains (and while desired by this user, isn't probably a change for the better).
I meant why have nil
at all when there’s already a less magic .none
that would lead a new programmer to the actual types and concepts involved. nil
is just … I don’t know, something that might trick people into thinking along the lines of null
in other languages?