Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Associated types and generics on protocols

I'm trying to declare a function in a protocol that forces types that conform to it to return a value of that same protocol but with a specific associated type:

protocol Protocol {
    typealias ValueType

    var value : ValueType? {get}

    func getProtocolString<A where A : Protocol, A.ValueType == String>() -> A
}

This compiles. It's when I try to create a class that conforms to it that I get the errors:

class AClass<T> : Protocol {
    var value : T?       

    func getProtocolString<A where A : Protocol, A.ValueType == String>() -> A {
        return AClass<String>()
    }
}

The error is 'AClass' is not convertible to 'A'.

Am I missing something? Is this even possible?

Thank you

like image 654
fbernardo Avatar asked Oct 31 '22 08:10

fbernardo


1 Answers

The problem lies with confusing a generic placeholder constrained by a protocol, with the protocol itself. Here’s a simpler example, similar to your code, to try and make it clear:

// first, define a protocol and two structs that conform to it
protocol P { }
struct S1: P { }
struct S2: P { }

// now, a function that returns an object in the form
// of a reference to protocol P
func f() -> P {
    // S1 conforms to P so that’s fine 
    return S1()
}
// ok all well and good, this works fine:
let obj = f()

// now, to do something similar to your example code,
// declare a generic function that returns a generic
// placeholder that is _constrained_ by P
// This will NOT compile:
func g<T: P>() -> T { return S1() }

Why does this not compile?

The way generic functions work is that at compile time, when you call the function, the compiler decides what type the placeholder T needs to be, and then writes you a function with all occurrences of T replaced with that type.

So with the example below, T should be replaced by S1:

let obj1: S1 = g()
// because T needs to be S1, the generic function g above is 
// rewritten by the compiler like this:
func g() -> S1 { return S1() }

This looks OK. Except, what if we wanted T to be S2? S2 conforms to P so is a perfectly legitimate value for T. But how could this work:

// require our result to be of type S2
let obj2: S2 = g()
// so T gets replaced with S2… but now we see the problem.
// you can’t return S1 from a function that has a return type of S2.
// this would result in a compilation error that S2 is not
// convertible to S1
func g() -> S2 { return S1() }

Here is the origin of the error message you are getting. Your placeholder A can stand in for any type that conforms to Protocol, but you are trying to return a specific type (AClass) that conforms to that protocol. So it won’t let you do it.

like image 166
Airspeed Velocity Avatar answered Nov 11 '22 03:11

Airspeed Velocity