Search code examples
swiftrecursionmacos-sierraswift-protocols

Swift recursive protocol: How to mark leave node?


I've a tree of (different) structs which I want to show in an NSOutlineView. I've also written an update function which determines the move/insert/reload operations on the outline view.

To make the update function more generic, I've written a protocol that makes the different structs similar:

protocol Outlinable: Equatable, Identifiable {
    associatedtype T: Outlinable

    var children: [T]? { get }
}

The children array is an optional to mark that a node might not have children. I've made two structs conform to this protocol:

struct ImageWithErrors: Decodable, FetchableRecord {
    let imageFile: ImageFile
    let imageFileErrors: [ImageFileError]
}

struct LayerImagesListItem: Decodable, FetchableRecord {
    let layer: Layer
    let imageFiles: [ImageWithErrors]
}

extension LayerImagesListItem: Identifiable, Outlinable {
    var id: Int64 { get { layer.id! }}
    var children: [ImageWithErrors]? { get { imageFiles }}
}

extension ImageWithErrors: Identifiable, Outlinable {
    var id: Int64 { get { -imageFile.id! }}
    var children: [Outlinable]? { get { return nil }}
}

The LayerImagesListItem is a root struct, while the ImageWithErrors is (currently) a leave struct. But on this last struct I get the errors:

Type 'ImageWithErrors' does not conform to protocol 'Outlinable'
Protocol 'Outlinable' can only be used as a generic constraint because it has Self or associated type requirements

I've tried replacing [Outlinable] with [Any] but that doesn't solve anything.

How can I tell Swift that ImageWithErrors is never going to return any children?


Solution

  • If ImageWithErrors can never have children, you could use Never.

    extension Never: Outlinable {
        var children: [Never]? { nil }
    }
    
    extension ImageWithErrors: Identifiable, Outlinable {
        var id: Int64 { get { -imageFile.id! }}
        var children: [Never]? { get { return nil }}
    }
    
    if let children = ImageWithErrors(imageFile: .init(), imageFileErrors: []).children {
        for child in children {
            print("there will never be any \(child)")
        }
    } else {
        print("children is always nil")
    }
    

    It is meant to be used:

    as the return type when declaring a closure, function, or method that unconditionally throws an error, traps, or otherwise does not terminate.

    but since ImageWithErrors.children is of type [Never] and always return nil that should not be an issue.