ryanday
ryanday

Reputation: 2516

Implicitly unwrapped multiplication and division

I've isolated the following code in a playground. In the playground, I get the noted compile time errors:

class MyClass {
    var weight: Double!

    func toOunces() {
        weight *= 0.035274          // 'Double!' is not identical to 'UInt8'
        weight = weight * 0.035274  // works
    }

    func toGrams() {        
        weight /= 0.035274          // 'Double!' is not identical to 'Float'
        weight = weight / 0.035274  // works
    }
}

I was following an example online using NSCoder, where decodeDoubleForKey() as Double? was being used, hence the implicitly unwrapped optional var weight: Double!.

I'm debating how correct this is to even do, and I fixed it in my code.

My question is, why these compile time errors? Why does multiplication compare to UInt8 and division compares to Float? Is there something I've been missing for years regarding *= and /= ?

I'm still learning this, so I'm probably missing some basic property.

Upvotes: 2

Views: 379

Answers (2)

Jeffery Thomas
Jeffery Thomas

Reputation: 42598

Well I think I have the answer, but you're not going to like it.

*= is defined as such

func *=(inout lhs: UInt8, rhs: UInt8)

func *=(inout lhs: Int8, rhs: Int8)

func *=(inout lhs: UInt16, rhs: UInt16)

func *=(inout lhs: Int16, rhs: Int16)

func *=(inout lhs: UInt32, rhs: UInt32)

func *=(inout lhs: Int32, rhs: Int32)

func *=(inout lhs: UInt64, rhs: UInt64)

func *=(inout lhs: Int64, rhs: Int64)

func *=(inout lhs: UInt, rhs: UInt)

func *=(inout lhs: Int, rhs: Int)

func *=(inout lhs: Float, rhs: Float)

func *=(inout lhs: Double, rhs: Double)


/// multiply `lhs` and `rhs` and store the result in `lhs`, trapping in
/// case of arithmetic overflow (except in -Ounchecked builds).
func *=<T : _IntegerArithmeticType>(inout lhs: T, rhs: T)

func *=(inout lhs: Float80, rhs: Float80)

/= is defined as

func /=(inout lhs: Float, rhs: Float)

func /=(inout lhs: Double, rhs: Double)

func /=(inout lhs: Float80, rhs: Float80)


/// divide `lhs` and `rhs` and store the result in `lhs`, trapping in
/// case of arithmetic overflow (except in -Ounchecked builds).
func /=<T : _IntegerArithmeticType>(inout lhs: T, rhs: T)

The / and * operators don't have an inout specifier on their first parameter, were as /= and *= do. The other thing I notice is UInt8 is the first *= operator and Float is the first /= operator.


Putting this all together, Double! can be coerced into Double, but cannot be coerced into inout Double. In both cases the error message is picking the first version of the operator method to report in the error.

Upvotes: 1

Connor
Connor

Reputation: 64674

You can make the *= operator work by explicitly unwrapping the optional first like so:

func toOunces() {
    weight! *= 0.035274
}

You can see why this is looking at how *= is defined.

func *=(inout lhs: Double, rhs: Double)

An implicitly unwrapped optional can't be passed as an inout parameter because it is a wrapper around a Double (or nil.)

Non inout arguments can be unwrapped automatically because the function just needs the value which can be extracted from the optional automatically rather than a reference to the actual value. That's why the * operator works for Double! which is defined as such.

func *(lhs: Double, rhs: Double) -> Double

Adding the ! after weight in your code changes it from passing a reference to the optional to passing a reference to the Double encased in the Optional. If *= had a normal function syntax and we remove some sugar it might look like this:

func multiplyAssign (inout lhs: Double, rhs: Double){
    lhs = rhs * lhs
}
var weight: ImplicitlyUnwrappedOptional<Double> = 0.0
multiplyAssign(&weight, 10.0) //trying to pass reference to an ImplicitlyUnwrappedOptional<Double> (error)
multiplyAssign(&(weight!), 10.0) //passing a reference to a Double

Upvotes: 4

Related Questions