Indirect Members

Hello community,

With enums, you can have indirect cases:

enum Tree<T> {
   case Leaf(T)
   indirect case Node(Tree, Tree)
}

But with structs, last time I checked, you can not do

struct LinkedList<T> {
   var value : T
   indirect var tail : LinkedList<T>?
}

This may be tolerable for internal types, but what if you interface with a REST API from another company and some API call can return a type where the json schema is recursive? Do I just have to switch to reference types?

Maybe allowing to annotate optional struct members as indirect would be a cleaner solution in the future.

3 Likes

How about this:

struct LinkedList<T> {
  var value: T
  var _tail: [LinkedList<T>]
  var tail: LinkedList<T>? {
    get { return _tail.first }
    set { _tail = [newValue] }
  }
}
5 Likes

The naive implementation of this would have to allocate for every new value, FWIW. Presumably you’re imagining Optional<Box<LinkedList>>, but it would actually be Box<Optional<LinkedList>> unless we made an exception for optionals.

4 Likes

Maybe Swift should define a (back-ported) Box type in the standard library so the ecosystem could use the same one.

public final class Box<T> { 
    public var wrappedValue: T { get set }
    public init(wrappedValue: T)
}

Then you can define your linked list as

struct LinkedList<T> {
   var value : T
   var tail : Box<LinkedList<T>>?
}

You may be interested in this pitch for the same feature:

3 Likes

ahhhh thanks, the preview didn't recommend this one as "similar" to me

Would be logical to have it available out of the box for structs.

For the time being you could use a simple workaround:

struct Node {
    let payload: Int
    let next: IndirectOptional<Node>
}

where:

indirect enum IndirectOptional<T> {
    case none
    case some(T)
}
3 Likes

The problem with the "wrap the property type in a box" approach is that it forces a separate heap allocation for each indirect property, which is going to increase memory fragmentation. This is fine if you just have a single property, but if you start adding more indirect properties, you're going to want them to be contained in the same "box".

Someone could probably develop a macro-based solution here that would extract the relevant properties into a storage class, but this is something that the language should really support directly because there are subtle inefficiencies/feetguns if you do it wrong (or even do it right): you have to implement copy-on-write yourself if your type is mutable, you have to write forwarding accessors and make sure they don't become a performance bottleneck, the box class adds useless metadata to the binary that you don't actually need/want, etc.

5 Likes