skip to Main Content

I have the struct Vector:

struct Vector{
var X : Measurement<Dimension>
var Y : Measurement<Dimension>
var Z : Measurement<Dimension>
...

And I create new object, for example:

let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

Everything works fine. But if try to use variable from other class, I got the error: "Cannot convert value of type ‘Measurement’ to type ‘Measurement’ in coercion"

final class SettingsManager{
  ...
 var test  = Measurement(value: 1.5, unit: UnitLength.inches)
  ...  }
class Calculator {
 ...
 let test = SettingsManager.shared.test as Measurement<Dimension>
 var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))

I tried "as!" and got

Cast from 'Measurement<UnitLength>' 
to unrelated type 'Measurement<Dimension>' 
always fails

The same variable declared in this class works fine as I showed above. What I did wrong?

3

Answers


  1. When you are declaring the test variable inside the SettingsManager you are not explicitly mentioning a type. So the swift’s type inference algorithm decides the type of the variable depend on the second parameter in Measurement(...).

    In this case test has the type of Measurement<UnitLength>. If you used passed UnitArea.acres as the second parameter your variable will have the type Measurement<UnitArea>. Not Measurement<Dimension>.

    If you mention the type explicitly as Measurement<Dimension> in the first place it will solve the issue.

    var test : Measurement<Dimension>  = Measurement(value: 1.5, unit: UnitLength.inches)
    

    Now lets check why your casting fails. You can cast a type to its sub type or super type. UnitLength is a subtype of Dimension. So you can cast between them.

    But Measurement<UnitLength> is NOT a subtype of Measurement<Dimension>. Why ?

    The reason is simple. Generics are invariant. This means that even if a generic type wraps a subtype, it doesn’t make it a subtype of a generic wrapping its superclass.

    For more clarification about the above paragraph read this.

    Login or Signup to reply.
  2. A Measurement<UnitLength> is not a kind of Measurement<Dimension>. These are unrelated types. It only works here:

    let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
    var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))
    

    because the Measurement(...) calls here are actually creating Measurement<Dimension>s, since that is what is being expected at the callsite. as Measurement<Dimension> tells it that you want a Measurement<Dimension>, and the parameter type of x and z are also Measurement<Dimension>. The type inference algorithm is smart enough to see that you must also mean Measurement<Dimension>(...).

    Measurement<Dimension>.init takes a Dimension as its second parameter, and UnitLength is a subtype of that, so no problems there.


    On the other hand, in SettingsManager, you declared test like this:

    var test = Measurement(value: 1.5, unit: UnitLength.inches)
    

    Nowhere here did you mention Measurement<Dimension>, so the type inference algorithm just uses the second parameter to infer that you must mean Measurement<UnitLength>, and so the type of test is Measurement<UnitLength>.

    If you just add as Measurement<Dimension>

    var test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
    

    it should work.

    However, wouldn’t it make more sense to for all three components of the vector to have the same type of unit?

    struct Vector<T: Dimension>{
        var X : Measurement<T>
        var Y : Measurement<T>
        var Z : Measurement<T>
    }
    
    Login or Signup to reply.
  3. This is just to lay to rest the question of why this is legal:

    let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension>
    var lVector = Vector(x: Measurement(value: 10, unit: UnitLength.meters), y: test, z: Measurement(value: 10, unit: UnitLength.meters))
    

    This shouldn’t even be a question; it’s just the normal substitution principle. Let’s take a simpler way of expressing it. What if you don’t cast?

    let test = Measurement(value: 1.5, unit: UnitLength.inches)
    

    Then test is inferred as Measurement<UnitLength>. Now let’s cast:

    let test: Measurement<Dimension> = Measurement(value: 1.5, unit: UnitLength.inches)
    

    That’s legal. Why? It’s not because Measurement<UnitLength> is a subtype of Measurement<Dimension>. It isn’t! It’s because UnitLength is a subtype of Dimension. It is as if you had said

    let test = Measurement(value: 1.5, unit: UnitLength.inches as Dimension)
    

    Do you see? You didn’t actually cast Measurement<UnitLength> to Measurement<Dimension>; that is impossible. You cast UnitLength up to Dimension, which is perfectly possible. It’s no different than if you had said

    let test2: Dimension = UnitLength.inches
    

    You’re always allowed to substitute a subtype where a supertype is expected. The difficulty in the original question is that Measurement<UnitLength> is not a subtype of Measurement<Dimension> (because Swift generics are not automatically covariant over their parameterized type).

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search