Using iterators of different types uniformly

In my code I want to assign Iterators of diferent types (but with Elements of the same type) to the same variable.

struct ChainedSequence<C1: Collection, C2: Collection>: Sequence 
  where C1.Element == C2.Element {
    var c1: C1
    var c2: C2

    func makeIterator() -> AnyIterator<C1.Element> {
        var iter = c1.makeIterator()  
        return AnyIterator<C1.Element> {
          if let el = iter.next() {
            return el
          } else {
            iter = c2.makeIterator() 
            return iter.next()
          }
          return nil
        }
    }
}

The compiler shows this error:

error: cannot assign value of type 'C2.Iterator' to type 'C1.Iterator'
            iter = c2.makeIterator() 
                   ~~~^~~~~~~~~~~~~~
                                     as! C1.Iterator

I tried to specify an opaque type for iter:

var iter: some IteratorProtocol = c1.makeIterator()

but then I get another error:

error: cannot convert return expression of type '(some IteratorProtocol).Element' to return type 'C1.Element?'

What should be the type of iter in order for this code to compile?

You won't be able to make it compile by "just" using some special type (well, except for Any) because IteratorProtocol is one of those "with Self or associated type requirements" and can't be used as a type.

You can erase both of them to AnyIterator, but honestly there's no reason — just use another variable:

    func makeIterator() -> AnyIterator<C1.Element> {
        var iter1 = c1.makeIterator()
        var iter2 = c2.makeIterator()
        return AnyIterator<C1.Element> {
          if let el = iter1.next() {
            return el
          } else {
            return iter2.next()
          }
        }
    }
1 Like

@wtedst thanks for your suggestion. Actually we started with exactly the same solution, but there are some issues with that:

  • after the first iterator gets exhausted, for each iteration of the second iterator we will still be calling next() on the first one
  • we've been thinking about making our class more general, supporting multiple collections, not only two.

All this brings the more general question: how to use multiple iterators uniformly? That's why I put such title on this topic.

Oh, I see. The underlying issue is that iterators can be of any type, so you need some kind of type erasure.

I also believe you don't have to store the underlying sequences altogether. I think this should work:


struct ChainedSequence<Element>: Sequence {
    
    private var iterators: [AnyIterator<Element>] = []
    
    init<S: Sequence>(startSequence: S) where S.Element == Element {
        iterators.append(AnyIterator(startSequence.makeIterator()))
    }
    
    mutating func add<S: Sequence>(_ sequence: S) where S.Element == Element {
        iterators.append(AnyIterator(sequence.makeIterator()))
    }
    
    func makeIterator() -> some IteratorProtocol {
        var current = 0
        return AnyIterator<Element> {
            if let next = sequences[current].next() {
                return next
            }
            
            while current < sequences.count - 1 {
                current += 1
                if let next = sequences[current].next() { return next }
            }
            
            return nil
        }
    }
}


var chain = ChainedSequence(startSequence: [1,2,3])
chain.add([4:4, 5:5, 6:6].values)

for num in chain {
    print(num)
}

Terms of Service

Privacy Policy

Cookie Policy