I've contrived this code to illustrate my question as simply as possible. If I have a generic function like this:
func get<T>() -> T? {
// For example purposes I'm hard coding the result.
return 5 as? T
}
if let x = get() as? Int { /* Won't compile: Generic parameter 'T' could not be inferred */ }
if let x = get() as Int? { /* true */ }
if let x: Int? = get() { /* true */ }
Now my question is why does the first one not compile?
I can use the other forms, but that's the one I would like to use so I'm wondering just why I can't.
CodePudding user response:
This could be because of the difference between as
and as?
. The key difference is that if you use as
, the compiler must be able to tell that the conversion is valid/will succeed, at compile time.
Because of this, there is a limit on what actual type get
could return. Eventually, the type checker decides that T
is Int
. Keep in mind that the Swift typechecker works on a constraint system.
On the other hand, if you use as?
, the LHS expression can be any type, and there are no compile time checks. The cast will be checked at runtime, and if it fails, the expression evaluates to nil. As a result, as?
does not limit what the type of its LHS expression is.
I've found the relevant section of code in the Swift compiler's source code, so you can explore it further on your own, if you desire. As you can see, in visitCoerceExpr
(foo as T
), it does this:
// Add a conversion constraint for the direct conversion between
// types.
CS.addExplicitConversionConstraint(fromType, toType, RememberChoice,
locator);
which it does not do in visitConditionalCheckedCastExpr
(foo as? T
).
Note that if you don't use T
anywhere else inside get
, you can just return Any
, and let the caller cast. This will allow you to use the as? Int
syntax.
func get() -> Any {
// ...
return 5
}
if let x = get() as? Int { /* ... */ }
CodePudding user response:
Sweeper's answer is excellent (as usual); I just want to put the same point another way, perhaps more simply. It's just a matter of the difference between telling and asking.
The compiler needs to be told what T is. That is what resolving / specializing a generic is about. When you say as Int?
or :Int?
, that is exactly what you are doing. You are telling the compiler that this thing is an Int?
(and so the compiler can reason that T
is Int
).
But when you say as?
, you are not doing that at all. This has nothing to do with Optionals or any other type. You are simply asking (the runtime) what type this is. You are not telling the compiler (or anyone) anything. So the compiler doesn't know and you can't compile.