# Async assignment to a lazy local var causes Xcode 15.3 compiler crash

**URL:** <https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281>\
**Category:** Using Swift\
**Tags:** concurrency, xcode, crash, actors\
**Created:** [April 16, 2024, 4:28am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281 "2024-04-16T04:28:25Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![GuyB](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/guyb/32/12116_2.png) [@GuyB](https://forums.swift.org/u/GuyB)\
**Post date:** [April 16, 2024, 4:28am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/1 "2024-04-16T04:28:25Z")

</div>

[Please skip to the bottom of the thread - the cause of the compiler crash is assigning an async value to a local lazy var]

I am working on an app which communicates between multiple network devices simultaneously, mainly using the Network and CoreBluetooth frameworks which are both pre-concurrency.

I have frequently encountered a situation in my async code where I need to suspend, waiting for some other piece of code to be in a certain state, and/or monitor state changes.

After lots of hair pulling and misdirection, I had settled on the following actor which I use in multiple places, to arbitrate such exchanges between contexts. It encapsulates the state value and an AsyncStream of those values as they change, but limits access to the stream to one context at a time to avoid the unwritten restriction that AsyncStreams cannot be iterated over by multiple contexts simultaneously. (As a side observation, I was surprised that I was able to create multiple iterators on the stream from different contexts without any runtime errors, but I found the behavior unpredictable).

```swift
actor AsyncState<State> where State: Equatable {

    private(set) var value: State {
        didSet {
            if oldValue != value {
                print("Scan State: \(String(describing: self.value))")
                stateContinuation?.yield(value)
            }
        }
    }

    private lazy var stateUpdates: AsyncStream<State> = {
        AsyncStream { (continuation: AsyncStream<State>.Continuation) -> Void in
            self.stateContinuation = continuation
        }
    }()
    private var stateContinuation: AsyncStream<State>.Continuation?

    init(state: State) {
        self.value = state
    }

    var streamSubscribed = false

    func obtainSubscription() -> AsyncStream<State> {
        if streamSubscribed { fatalError("Subscribing to AsyncState<\(State.Type.self)> stream multiple times")}
        streamSubscribed = true
        return stateUpdates
    }

    func relinquishSubscription() {
        streamSubscribed = false
    }

    func change(to newState: State) {
        let oldState = self.value
        if oldState == newState { return }
        print("Change State: from: \(oldState) to: \(newState)")
        self.value = newState
    }
}

```

Now this had been pretty successful until this morning when I managed to crash Xcode 15.3 with the following (simplified) code:

```swift
class Connection {

    enum State {
        case waitingToConnect, readyToPair, communicating, pairing, paired, unavailable, disconnected
    }
    enum MyError: Error {
        case notConnected
    }

    private let state = AsyncState<State>(state: .waitingToConnect)

    func waitTillPaired() async throws {

        var state: State? = await self.state.value

        // ***swift-frontend crashes on the following line 26***
        lazy var stateChanges = await self.state.obtainSubscription().makeAsyncIterator()
        defer {
            Task {
                await self.state.relinquishSubscription()
            }
        }

        repeat {
            guard state != .unavailable else { throw MyError.notConnected }
            if state == .paired {
                return
            }
            state = await stateChanges.next()
        } while state != nil
        throw MyError.notConnected
    }
}

```

With just the above in a fresh project, Xcode crashes with the following:

```plaintext
1.	Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
2.	Compiling with the current language version
3.	While evaluating request IRGenRequest(IR Generation for file ".../TestAsyncState/Connection.swift")
4.	While emitting IR SIL function "@$s14TestAsyncState10ConnectionC14waitTillPaired12reachability7timeoutyAA0bC0CySbG_SdtYaKF12stateChangesL_ScS8IteratorVyAC0C0O_Gvg".
 for getter for stateChanges (at .../TestAsyncState/Connection.swift:26:18)

```

I will report the error on Feedback Assistant, but am writing here to ask whether anyone thinks I am misusing the concurrency system, or if there is a better/cleaner/safer way to achieve the same outcome - i.e an ability to check, monitor or wait for a state change asynchronously.

---

<div class="post-metadata">

**Author:** ![taylorswift](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/taylorswift/32/43413_2.png) [@taylorswift](https://forums.swift.org/u/taylorswift)\
**Post date:** [April 16, 2024, 4:43am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/2 "2024-04-16T04:43:37Z")

</div>

you’re getting a compiler crash, which means it’s probably triggered by the shape of the code instead of the specific types in it. do you still get the crash when you replace the types with local substitutes?

---

<div class="post-metadata">

**Author:** ![GuyB](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/guyb/32/12116_2.png) [@GuyB](https://forums.swift.org/u/GuyB)\
**Post date:** [April 16, 2024, 5:10am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/3 "2024-04-16T05:10:09Z")

</div>

Good call. If I replace all references to AsyncStream with MyAsyncStream defined as follows, I still get the compiler crash. If I change `AsyncState` from `actor` to `class` then the code compiles (but is of course no longer thread safe).

```swift
public struct MyAsyncStream<Element> {

    public struct Continuation : Sendable {
        public enum YieldResult {
            case enqueued(remaining: Int)
        }

        @discardableResult
        public func yield(_ value: Element) -> MyAsyncStream<Element>.Continuation.YieldResult {
            return .enqueued(remaining: 0)
        }
    }

    public struct Iterator : AsyncIteratorProtocol {
        public mutating func next() async -> Element? {
            return nil
        }
    }

    public init(_ elementType: Element.Type = Element.self, _ build: (MyAsyncStream<Element>.Continuation) -> Void) {
    }

    public func makeAsyncIterator() -> MyAsyncStream<Element>.Iterator {
        return Iterator()
    }
}

```

---

<div class="post-metadata">

**Author:** ![GuyB](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/guyb/32/12116_2.png) [@GuyB](https://forums.swift.org/u/GuyB)\
**Post date:** [April 16, 2024, 5:26am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/4 "2024-04-16T05:26:06Z")

</div>

Further whittling down, the compiler crash still occurs if `waitTillPaired()` is simplified to:

```swift
    func waitTillPaired() async throws {
        lazy var stateChanges = await self.state.obtainSubscription().makeAsyncIterator()
        let state = await stateChanges.next()
    }

```

The code compiles correctly if I remove `lazy` or if I change AsyncState from `actor` to `class`.

So this may be an edge case with lazy evaluation of actor owned instance variables.

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [April 16, 2024, 6:57am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/5 "2024-04-16T06:57:20Z")

</div>

I believe it has nothing to do with actor or `AsyncStream`. I encountered the same crash with the following code.

```swift
func getX() async -> Int {
  return 10
}

func foo() async {
  lazy var x = await getX()
  print(x)
}

```

Btw, this code does not compile with Xcode 15.2 neither.

---

<div class="post-metadata">

**Author:** ![jaleel](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/jaleel/32/45076_2.png) [@jaleel](https://forums.swift.org/u/jaleel)\
**Post date:** [April 16, 2024, 7:38am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/6 "2024-04-16T07:38:46Z")

</div>

Can lazy properties be async? 🤔

---

<div class="post-metadata">

**Author:** ![GuyB](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/guyb/32/12116_2.png) [@GuyB](https://forums.swift.org/u/GuyB)\
**Post date:** [April 16, 2024, 10:41am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/7 "2024-04-16T10:41:48Z")

</div>

> [@CrystDragon](#):
>
> Btw, this code does not compile with Xcode 15.2 neither.

You are right, changing AsyncState from actor to class was simply making the access synchronous rather than async.

Likewise, if `getX()` is not async, the compiler does not crash.

---

<div class="post-metadata">

**Author:** ![CrystDragon](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/crystdragon/32/10461_2.png) [@CrystDragon](https://forums.swift.org/u/CrystDragon)\
**Post date:** [April 16, 2024, 11:20am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/8 "2024-04-16T11:20:28Z")

</div>

I think there's a solid reason why it's dangerous, because any read of that property potentially needs to be awaited.

```swift
func foo() async {
  lazy var x = await getX()
  print(x) // <- this should be an async operation
}

```

Nowadays there's no language rule supporting this.

---

<div class="post-metadata">

**Author:** ![taylorswift](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/taylorswift/32/43413_2.png) [@taylorswift](https://forums.swift.org/u/taylorswift)\
**Post date:** [April 16, 2024, 10:45pm UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/9 "2024-04-16T22:45:10Z")

</div>

> [@GuyB](#):
>
> The code compiles correctly if I remove `lazy` or if I change AsyncState from `actor` to `class`.
> 
> So this may be an edge case with lazy evaluation of actor owned instance variables.

edge cases still cut a lot of people! do you have a single-file reproduction of this compiler crash?

---

<div class="post-metadata">

**Author:** ![GuyB](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/guyb/32/12116_2.png) [@GuyB](https://forums.swift.org/u/GuyB)\
**Post date:** [April 17, 2024, 1:51am UTC](https://forums.swift.org/t/async-assignment-to-a-lazy-local-var-causes-xcode-15-3-compiler-crash/71281/10 "2024-04-17T01:51:37Z")

</div>

> [@taylorswift](#):
>
> edge cases still cut a lot of people! do you have a single-file reproduction of this compiler crash?

Yes, I have submitted a project which demonstrates the crash to the Feedback Assistant. An even simpler example than the one I submitted is:

```swift
actor AsyncEntity {
    var i = 0
}

class Crash {
    func here() async {
        let a = AsyncEntity()
        // Any async call in local lazy assignment will crash compiler
        lazy var crash = await a.i  
        print(crash)
    }
}

```
