2

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?

7
  • Believe the compiler. Generics are not covariant. There are no generic supertypes and subtypes. The two types really are unrelated.
    – matt
    Commented Feb 2, 2022 at 12:36
  • @matt that's all very fine and true, but it doesn't explain why let test = Measurement(value: 1.5, unit: UnitLength.inches) as Measurement<Dimension> works. I think I'd regard it as a bug in the language.
    – JeremyP
    Commented Feb 2, 2022 at 12:47
  • @JeremyP It's a comment, not an answer. Sweeper's answer is an answer.
    – matt
    Commented Feb 2, 2022 at 12:49
  • Anyway it does explain it just fine. Measurement<UnitLength> is not a Measurement<Dimension> but UnitLength is certainly a Dimension. Do you see?
    – matt
    Commented Feb 2, 2022 at 12:52
  • @matt I understand exactly why it doesn't work when it doesn't work, but the same reasoning should apply when you construct an object and immediately try to cast it in the same expression. There's an extra hidden rule to do with type inference that is a little surprising when you first come across it.
    – JeremyP
    Commented Feb 2, 2022 at 13:04

3 Answers 3

4

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>
}
2

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).

1
  • If someone needs more clarification about covariance and invariance of generics and subtypes read this
    – udi
    Commented Feb 4, 2022 at 5:49
0

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.

1
  • Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, can you edit your answer to include an explanation of what you're doing and why you believe it is the best approach? Please take a look at the other answers offered, as they provide good examples. Commented Feb 4, 2022 at 1:15

Not the answer you're looking for? Browse other questions tagged or ask your own question.