How to use sub protocol in generic type that use protocol?

Hello everyone, I have a type like this and I want the Model and ObjectProtocol to be strongly bound:

final public class Model<Object> where Object: ObjectProtocol {
	var object: Object

	public init(object: Object) {
		self.object = object
	}
}

And I would like to use Model like this:

protocol SubObjectProtocol: ObjectProtocol {
	var name: String { get }
}

let model: Model<SubObjectProtocol> = ..... // error: Type 'any SubObjectProtocol' cannot conform to 'ObjectProtocol'

I can only use some ObjectProtocol to initialize the Model, but I can't bind the generic Object with some ObjectProtocol.

final public class Model<Object> {
	var object: Object

	public init(object: some ObjectProtocol) { // No idea how to bind
		self.object = object // error: Cannot assign value of type 'some ObjectProtocol' to type 'Object'
	}
}

Any idea?

The generic argument should be a concrete type conforming to SubObjectProtocol?

2 Likes

As @ibex mentioned, you cannot pass in a protocol where a concrete type is expected.

If I understand the question correctly you want the generic Object to be constrained to only concrete types that conform to the SubObjectProtocol ? In that case, you could replace

Object: ObjectProtocol

with

Object: SubObjectProtocol

I made a snippet for you:

public protocol ObjectProtocol {
    var some: String {get set}
}

public protocol SubObjectProtocol: ObjectProtocol {
    var name: String { get }
}


final public class Model<Object> where Object: ObjectProtocol {
    var object:  Object
    
    public init(object: Object) { // No idea how to bind
        self.object =  object
    }
    func checkMe() {
        print ("\(object.some)") // Protocol has it
        switch object {
        case is RootObject: print ("Root has no name")
        case is SubObject: if let sub = object as? SubObject {
            print ("I have name: \(sub.name)")
        }
        default: print ("I don'y know who I'm")
        }
    }
}
struct RootObject: ObjectProtocol {
    var some = "I'm Root"
}

struct SubObject: SubObjectProtocol {
    var some = "I'm Sub"
    var name = "Kornelius Rep"
}

let rootObject: RootObject = RootObject()
let subObject: SubObject = SubObject()

let z = Model(object: rootObject)
let s = Model(object: subObject)

z.checkMe()
s.checkMe()
final public class Model2 {
    var object:  ObjectProtocol
    
    public init(object: some ObjectProtocol) { // No idea how to bind
        self.object =  object
    }
    func checkMe() {
        print ("\(object.some)") // Protocol has it
        switch object {
        case is RootObject: print ("Root has no name")
        case is SubObject: if let sub = object as? SubObject {
            print ("I have name: \(sub.name)")
        }
        default: print ("I don'y know who I'm")
        }
    }
}

let z1 = Model2(object: rootObject)
let s1 = Model2(object: subObject)

z1.checkMe()
s1.checkMe()
///------ OR ---------
final public class Model2 {
    var object:  ObjectProtocol
    
    public init(object: some ObjectProtocol) { // No idea how to bind
        self.object =  object
    }
    func checkMe() {
        print ("\(object.some)") // Protocol has it
        switch object {
        case is RootObject: print ("Root has no name")
        case is SubObject: if let sub = object as? SubObject {
            print ("I have name: \(sub.name)")
        }
        default: print ("I don'y know who I'm")
        }
    }
}

let z1 = Model2(object: rootObject)
let s1 = Model2(object: subObject)

z1.checkMe()
s1.checkMe()

Hi @NotTheNHK SubObjectProtocol here can be any type that inherits from ObjectProtocol.

I need ObjectProtocol for Model to satisfy certain requirements, and I need to implement the type for SubObjectProtocol in another package, so it is better for it to be a protocol.

Hi @typoland

The place where I create the Model object and the place where I implement RootObject/SubObject are in different packages, so I have to use ObjectProtocol/SubObjectProtocol to define the Model type.

I added alternative add the bottom, after --OR-- comment

Now. (forget so save edit)

As I said before, I can't obtain the actual implementation type of ObjectProtocol where I use Model, so I can't use RootObject from your code to create Model.

@miku1958 This should be a good basis for the rest of Model<Object>.

final public class Model<Object> where Object : ObjectProtocol {
	var object: Object
	
	public init(object: Object) {
		self.object = object
	}
	
	/// Nillable as ``ObjectProtocol`` cannot be guaranteed to be exclusively ``Object``
	/// - Note: Performs ``init(object: Object)`` after coercing `object` 
	/// - Parameter object: Value that should be the same type as ``Object``. `Object` is always contained in `some ObjectProtocol`
	/// - Returns: `nil` is provided when `object.Type` isn’t ``Object``
	public convenience init?(object: ObjectProtocol) {
		guard let object: Object = object as? Object else { return nil }
		self.init(object: object)
	}
}
1 Like

This can be a candidate, but it does not actually bind the type. I can pass any ObjectProtocol type that is different from Object.

Then, you will get a nil Model, which you have to cope with. :slight_smile:

Yes, but this will only result in an error at runtime.

Yes, unfortunately.

I understand what you want, but I am not sure if it can be done in current Swift.

Maybe, @Slava_Pestov can elaborate.

1 Like

You can freely change the type of object in the nillable initializer between some and any. It depends on context if it should be abstract generic or specialized generic.

You can use the provided Model.init?(object:) like so:

guard let demoModel = Model<ParticularConformingType>(object: ConformingType()) else {
    expectationFailure(“`demoModel.object` wasn’t the expected type \(ParticularConformingType.Type)”)
}