Capturing a noncopyable type in an async closure

i didn’t think this would compile, but it does:

import Atomics

extension HTTP
{
    struct TimeCop:~Copyable, Sendable
    {
        let epoch:UnsafeAtomic<UInt>

        init()
        {
            self.epoch = .create(0)
        }

        deinit
        {
            self.epoch.destroy()
        }
    }
}
let cop:HTTP.TimeCop = .init()
async
let _:Void =
{
    try await cop.start(interval: .milliseconds(1000))
    try await connection.channel.close()
}()

capturing this thing in a closure will end badly.

double free or corruption (fasttop)

the problem is that you can’t capture the noncopyable type in an escaping closure, and it needs to take it as a borrowing parameter:

async
let _:Void =
{
    (cop:borrowing HTTP.TimeCop) in

    try await cop.start(interval: .milliseconds(1000))
    try await connection.channel.close()

} (cop)

but why on earth did the capturing one compile in the first place?

2 Likes

Nice catch! Does it reproduce in a function body? It wouldn’t surprise me if this was a bug in top-level code handling. (My guess is it’s expecting to be able to move the value into the closure, but it fails to.)

it is in a function scope, i undented it to make it more readable for the forums.

it raises the expected error if i'm explicit about the capture with a [cop] in.

See, that should be fine because captures of locals capture the variable, not the value. The value should still end up being destroyed exactly once.

2 Likes

i got this down to a minimal reproducer which i filed as a bug here:

4 Likes