3

How can one conform an actor to the Sequence protocol? The following code generates the compiler warning:

Instance method 'makeIterator()' isolated to global actor 'MainActor' can not satisfy corresponding requirement from protocol 'Sequence'

@MainActor class Test: Sequence {
    private var contents: [Int] = []
    
    func makeIterator() -> Array<Int>.Iterator {
        contents.makeIterator()
    }
}
George
  • 25,988
  • 10
  • 79
  • 133
Philip Pegden
  • 1,732
  • 1
  • 14
  • 33
  • 2
    I don't believe an Actor can conform to Sequence. Its values cannot be accessed without awaiting, which is required by Sequence. You should be able to conform it to AsyncSequence. – Rob Napier Aug 10 '21 at 16:03
  • My actual goal is to conform to `RandomAccessCollection`. I had hoped starting with conformance to `Sequence` this was going to be a good step to help me learn, but I'm realising how much Swift concurrency has turned my understanding upside down. – Philip Pegden Aug 10 '21 at 16:17
  • Same thing; an Actor can't conform to RandomAccessCollection. That would be particularly problematic, since the `count` and elements could change behind the caller's back. (If the contents are immutable, there's no point to it being an Actor.) Instead the Actor should provide its contents as a property/method. For example, `let elements = await actor.elements`. Then `elements` would be a snapshot of the Actor's contents at some point in time, and it could definitely conform to RandomAccessCollection. – Rob Napier Aug 10 '21 at 17:10
  • 1
    @RobNapier An actor can still conform to `Sequence` (or any non-async protocol such as `Hashable`) so long as it marks the required methods as `nonisolated`. Of course, you would not be able to access any of the actor-isolated properties, which means an actor might not be ideal for `Sequence`. `AsyncSequence` is a better fit for an actor – Left as an exercise Aug 11 '21 at 13:45

1 Answers1

6

Applying a global actor, like @MainActor, to a type declaration adds an implicit @MainActor in that type's properties and methods.

This means that your example is equivalent to:

class Test: Sequence {
    @MainActor private var contents: [Int] = []
    
    @MainActor 
    func makeIterator() -> Array<Int>.Iterator {
        contents.makeIterator()
    }
}

This is an issue because Sequence has a makeIterator requirement that is not isolated to a specific global actor. If this was the case you wouldn't be able to use a for-loop on an Array in a background thread. So, to opt-out of the implicit isolation, you can write:

@MainActor class Test: Sequence {
    private var contents: [Int] = []
    
    nonisolated func makeIterator() -> Array<Int>.Iterator {
        // You can't use the isolated property 'contents'
        // here, because it's isolated
    }
}

Perhaps you intend to make Test conform to a Sequence-like protocol whose requirements are isolated to @MainActor. Unfortunately, a similar feature has only been proposed as a future direction.

If you are still confused, it's worth taking a step back and thinking about what you really want to achieve. Actor isolation offers a way to safely mutate data shared across threads. Thus, no isolation implies either a lack of mutation (hence the ability to mark let properties nonisolated) or a synchronization mechanism other than actors.

Filip Sakel
  • 315
  • 4
  • 8