Reputation: 10589
When I do the following:
let gapDuration = Float(self.MONTHS) * Float(self.duration) * self.gapMonthly;
I get the error:
Binary operator '*' cannot be applied to operands of type 'Float' and 'Float!'
But when I do:
let gapDuration = 12 * Float(self.duration) * self.gapMonthly;
Everything is working fine. I have no Idea what this error is telling me.
self.gapMonthly
is of type Float!
and self.duration
and self.MONTHS
are of type Int!
Upvotes: 2
Views: 3639
Reputation: 80811
I would consider this a bug (at the very least, the error is misleading), and appears to be present when attempting to use a binary operator on 3 or more expressions that evaluate to a given type, where one or more of those expressions is an implicitly unwrapped optional of that type.
This simply stretches the type-checker too far, as it has to consider all possibilities of treating the IUO as a strong optional (as due to SE-0054 the compiler will treat an IUO as a strong optional if it can be type-checked as one), along with attempting to find the correct overloads for the operators.
At first glance, it appears to be similar to the issue shown in How can I concatenate multiple optional strings in swift 3.0? – however that bug was fixed in Swift 3.1, but this bug is still present.
A minimal example that reproduces the same issue would be:
let a: Float! = 0
// error: Binary operator '*' cannot be applied to operands of type 'Float' and 'Float!'
let b = a * a * a
and is present for other binary operators other than *
:
// error: Binary operator '+' cannot be applied to operands of type 'Float' and 'Float!'
let b = a + a + a
It is also still reproducible when mixing in Float
expressions (as long as at least one Float!
expression remains), as well as when explicitly annotating b
as a Float
:
let b: Float = a * a * a // doesn't compile
let a: Float! = 0
let b: Int = 0
let c: Int = 0
let d: Float = a * Float(b) * Float(c) // doesn't compile
A simple fix for this would be to explicitly force unwrap the implicitly unwrapped optional(s) in the expression:
let d = a! * Float(b) * Float(c) // compiles
This relieves the pressure on the type-checker, as now all the expressions evaluate to Float
, so overload resolution is much simpler.
Although of course, it goes without saying that this will crash if a
is nil
. In general, you should try and avoid using implicitly unwrapped optionals, and instead prefer to use strong optionals – and, as @vadian says, always use non-optionals in cases where the value being nil
doesn't make sense.
If you need to use an optional and aren't 100% sure that it contains a value, you should safely unwrap it before doing the arithmetic. One way of doing this would be to use Optional
's map(_:)
method in order to propagate the optionality:
let a: Float! = 0
let b: Int = 0
let c: Int = 0
// the (a as Float?) cast is necessary if 'a' is an IUO,
// but not necessary for a strong optional.
let d = (a as Float?).map { $0 * Float(b) * Float(c) }
If a
is non-nil, d
will be initialized to the result of the unwrapped value of a
multiplied with Float(b)
and Float(c)
. If however a
is nil
, d
will be initialised to nil
.
Upvotes: 6