Home > Software engineering >  Unable to compare two instances of opaque types
Unable to compare two instances of opaque types

Time:05-08

It's interesting to see that we can now declare return values that are protocols with associated types.

The following throws an error and I don't understand the error.

protocol Plant: Equatable {
    var maintenance: String { get }
}

struct Rose: Plant {
    let height: Float // inches
    let maintenance = "Water once a day."
}

struct Orchid: Plant {
    let height: Float // inches
    let maintenance = "Water once a month."
}

func makePlant1() -> some Plant {
    Rose(height: 4)
}

func makePlant2() -> some Plant {
    Orchid(height: 8)
}

let plant1: some Plant = makePlant1()
let plant2: some Plant = makePlant2()

func asdf() {
    let condition: Bool = (plant1 == plant2) // error: ambiguous without more context
    print(condition)
}

CodePudding user response:

When you declare a function to return some Plant then the compiler will look at the content of the function to see what real type is returned and in your example you are returning different types, Rose and Orchid.

This breaks the requirement from Equatable that Self must be the same, that you use some doesn't mean that this requirement disappears.

The error message you get is a bit confusing but if you change it to

print(plant1 == plant2)

the error is more informative

Cannot convert value of type 'some Plant' (result of 'makePlant2()') to expected argument type 'some Plant' (result of 'makePlant1')

To be able to use == here you need to use objects created by the same function

func makePlant1(_ value: Float) -> some Plant {
    Rose(height: value)
}

let plant1 = makePlant1(4)
let plant2 = makePlant1(4)
let plant3 = makePlant1(6)

print(plant1 == plant2) // true
print(plant1 == plant3) // false

Here is an informative article on the topic from hackingwithswift.com

CodePudding user response:

The protocol Plant conforms to Equatable. This means that the types that conform to Plant also conform to Equatable.

Rose conforms to Plant, so it's also Equatable: you can differentiate two Roses.

Same for Orchid: you can differentiate two Orchids.

But you can't equate a Rose to an Orchid: they both conform to the same protocol, but they don't inherit from the same class.

If you try to equate two of the same type, it works - see examples below.

1) Equating instances that follow the same protocol

protocol Plant: Equatable {
    var maintenance: String { get }
}

struct Rose: Plant {
    let height: Float // inches
    let maintenance = "Water once a day."
}

struct Orchid: Plant {
    let height: Float // inches
    let maintenance = "Water once a month."
}

    func makePlant2() -> some Plant {
        Orchid(height: 8)
    }

    func makePlant3() -> some Plant {
        Orchid(height: 10)
    }

    func compare() {
        let plant2: Orchid = makePlant2() as! Orchid
        let plant3: Orchid = makePlant3() as! Orchid

        let conditionPlant: Bool = (plant3 == plant2) // both are Equatable of the same type
        print(conditionPlant)
    }

2) Equating classes that inherit from the same type

protocol Plant: Equatable {
    var maintenance: String { get }
}

class Tree: Plant {
    static func == (lhs: Tree, rhs: Tree) -> Bool {
        lhs.name == rhs.name
    }
    
    let name: String
    var maintenance = "Never"
    
    init(name: String) {
        self.name = name
    }
}

class Oak: Tree { }
class Sequoia: Tree { }

    func makeTree1() -> Tree {
        Oak(name: "Shady oasis")
    }
    
    func makeTree2() -> Tree {
        Sequoia(name: "Old woody")
    }

    func compare() {
        let tree1 = makeTree1()
        let tree2 = makeTree2()

        let conditionTree: Bool = (tree1 == tree2) // both are Equatable, inherit from the same type
        print(conditionTree)
    }
  • Related