23

I'm modeling a owner/ownee scheme on Swift:

class Owner<T: Ownee> {
     // ...
}

protocol Ownee {
    var owner: Owner<Self> { get }
}

Then I have a pair of classes professor/student that adhere to the modeled types above:

class Professor: Owner<Student> {
    // ...
}

class Student: Ownee {
    let professor: Professor
    var owner: Owner<Student> {  // error here (see below)
        return professor
    }

    init(professor: Professor) {
        self.professor = professor
    }
}

However I get the following error on the definition of var owner in the Student class:

Protocol 'Ownee' requirement 'owner' cannot be satisfied by a non-final class ('Student') because it uses 'Self' in a non-parameter, non-result type position

I'm trying to understand what's the cause for this error, why making the class Student final would fix it, and if there's some workaround to be able to model this differently, without making this class final. I've googled about that error, but haven't found much so far.

Ernesto
  • 3,837
  • 6
  • 35
  • 56
  • 1
    What happens if `Student` is subclassed? The `owner` property remains `Owner`, but `Student != StudentSubclass` – Alexander May 10 '16 at 14:24
  • 1
    @AMomchilov that is true, but is that a problem? And if so, why? – luk2302 May 10 '16 at 14:25
  • 1
    Yes, because `Student` is conforming to the `Ownee` protocol, whose type constraints states that the `owner` must be a an `Owner`, where `Self` refers to the conforming type, `Student`. Subclassing would breech this contract, and thus must not be allowed, so the compiler suggests you make `Student` final. – Alexander May 10 '16 at 14:28

4 Answers4

19

The Error is correct. You have to make your class final, since no subclasses could conform your protocol Ownee.

Consider this subclass:

class FirstGradeStudent: Student {
   // Student contains following variable:
   // var owner: Owner<Student> {
   //     return professor
   //  }
}

As you can see, it would have to implement var owner: Owner<Student> because of his parent, but it should be implementing var owner: Owner<FirstGradeStudent> instead, because the protocol contains var owner: Owner<Self> { get } and in this case Self would be FirstGradeStudent.

Workaround

1: Define a superclass to Ownee, it should be used by Owner:

class Owner<T: OwneeSuper> {
    // ...
}

protocol OwneeSuper {}    
protocol Ownee: OwneeSuper {
    associatedtype T: OwneeSuper
    var owner: Owner<T> { get }
}

OwneeSuper is just a workaround to overcome this problem, otherwise we would just be using:

protocol Ownee {
    associatedtype T: Ownee
    var owner: Owner<T> { get }
}

2. In classes that conform to Ownee, you must turn the abstract type of the associatedtype into a concrete class by defining a typealias:

class Student: Ownee {
    typealias T = Student // <<-- define the property to be Owner<Student>
    let professor: Professor
    var owner: Owner<T> { 
        return professor
    }

    init(professor: Professor) {
        self.professor = professor
    }
}

3. Subclasses can now make use of the property, which will be of your defined type:

class FirstGradeStudent: Student {
    func checkOwnerType() {
        if self.owner is Owner<Student> { //warning: 'is' test is always true
            print("yeah!")
        }
    }
}
Daniel
  • 20,420
  • 10
  • 92
  • 149
  • Thanks for your answer. I wanted to know if you would know a workaround for modeling what I intend to. I want `Ownee` objects to have a reference to the `Owner` instance to which they belong. My intent was that once defined in `Student`, owner would always be of type `Owner` which clearly can be the owner of `Student` instances and also of instances of subclasses of `Student`. I expected that `FirstGradeStudent().owner` would also be of type `Owner` too. – Ernesto May 10 '16 at 16:34
10

The following syntax should support what you're after:

protocol Ownee {
    associatedtype Owned = Self where Owned:Ownee
    var owner: Owner<Owned> { get }
}

(tested using Swift 4 - Beta 1)

David James
  • 2,430
  • 1
  • 26
  • 35
7

What happens if Student is subclassed? The owner property remains Owner<Student>, but Student != StudentSubclass.

By making your Student class conform to the Ownee protocol, you must satisfy the contract of the protocol. The Ownee protocol states that Owner has type constraint, such that the Owner generic type is the type that's conforming to Ownee (Student, in this case).

If the compiler were to allow subclassing (i.e. by allowing you to not make Student final), then it would be possible for a StudentSubclass to exist. Such a subclass would inherit the Owner property, of type Owner<Student>, but Student is not the same as StudentSubclass. The Ownee protocol's contract has been breached, thus, such a subclass cannot be allowed to exist.

Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Ok, got it. But is there a workaround for modeling what I intend to model? I was expecting that once the `owner` property is satisfied by `Student`, then classes inheriting from it would just use that, and that `StudentSubclass(professor: p).owner === p` would hold. – Ernesto May 10 '16 at 14:40
  • 1
    You can change the type constraint to `T: Self`, so that subclasses of the conforming type become acceptable. When `owner` is satisfied by `Student`, it must be a `Owner` type. No subclass can exist without breaking the requirement that the generic type of `Owner` be the same as the conforming type – Alexander May 10 '16 at 14:51
  • I actually tried, and apparently it does not support that notation in that context. It gives the error `Expected '>' to complete generic argument list` where the `:` is located in `Owner`. – Ernesto May 10 '16 at 15:36
  • 1
    The type constraint ("``") goes in the protocol declaration, whilst just the generic type ("``") is used in the definition of `Owner`. Here's an example of what it should look like: http://stackoverflow.com/q/24089145/3141234 – Alexander May 10 '16 at 15:43
  • Sorry for the back-and-forth misunderstanding, but that example you linked to only has functions with generic types. I do not see any protocol declaration with a `` or with any generic type whatsoever. And whenever I try to put a generic type in a protocol declaration (i.e. `protocol Name ...`), it gives an error. It is my understanding that protocols do not support generic types, only associated types. Isn't that so? – Ernesto May 10 '16 at 16:28
  • I'm not near my Mac to test this and I don't know this stuff off the top of my head – Alexander May 10 '16 at 17:03
  • 1
    @Ernesto yes, you are right, protocols do not support generic types, only associated types – Daniel May 11 '16 at 08:06
  • Why doe this does not throw an error in case of a function, such as `func myType()->Self` ? – G.Abhisek Jul 11 '20 at 08:21
  • @G.Abhisek I don't really understand your question. – Alexander Jul 11 '20 at 13:13
5

If you are coming to this thread just looking for a way to have subclasses inherit a method referring to its own type (type of Self) one workaround would be to parameterize the parent class on the child's type. e.g.

class Foo<T> {
    func configure(_ block: @escaping (T)->Void) { }
}

class Bar: Foo<Bar> { }

Bar().configure { $0... }

BUT... it also seems that you can add a method in a protocol extension that would not be considered to conform in the protocol itself. I don't fully understand why this works:

protocol Configurable {
    // Cannot include the method in the protocol definition
    //func configure(_ block: @escaping (Self)->Void)
}

public extension Configurable {
    func configure(_ block: @escaping (Self)->Void) { }
}

class Foo: Configurable { }

class Bar: Foo { }

Bar().configure { $0... }

If you uncomment the method in the protocol definition itself you'll get the Protocol 'Configurable' requirement 'configure' cannot be satisfied by a non-final class ('Foo') because it uses 'Self' in a non-parameter, non-result type position compile error.

Pat Niemeyer
  • 5,930
  • 1
  • 31
  • 35