简体   繁体   中英

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

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.

It seems that you are a little misunderstanding generics. Generic functions are instantiated at call sites of these, not at each function itself body. So, the type constraints you've written are saying that this function returns a value, a type of which may be any of all the subtypes of the Protocol . Consequently, the function definition must be statically correct about A for all the subtypes of Protocol , not only for AClass<String> , which is only an one type of Protocol .

In any case, I think there is no direct way to achieve what you want, at least in current Swift.

This seemed to work in the playground... does it work for what you are trying to do?

protocol StringProtocol
{
    typealias ValueType

    var value : ValueType? { get }

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

class StringClass : StringProtocol
{
    typealias ValueType = String

    var value : ValueType?

    init() { }

    func getProtocolString<A where A: StringProtocol, A.ValueType == String>() -> A
    {
        return StringClass() as A
    }
}

I'm still not exactly following what requirements you are trying to fulfill with this implementation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM