Mulgard
Mulgard

Reputation: 10589

Binary operator '*' cannot be applied to operands of type 'Float' and 'Float!'

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

Answers (1)

Hamish
Hamish

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

Related Questions