~Copyable: what is it good for?

despite ample enthusiasm, i have struggled to apply ~Copyable in any productive manner. perhaps i have simply not learned the correct patterns yet.

here’s a noncopyable struct of atomic counters:

import Atomics 

struct Counters:~Copyable
{
    let foosBarred:UnsafeAtomic<Int>
    let barsFooed:UnsafeAtomic<Int>

    //  many many more atomics

    init()
    {
        self.foosBarred = .create(0)
        self.barsFooed = .create(0)
        ...
    }

    deinit
    {
        self.foosBarred.destroy()
        self.barsFooed.destroy()
        ...
    }
}

it is owned by an actor:

final
actor ServerLoop
{
    private nonisolated
    let plugins:Plugins
    private nonisolated
    let count:Counters

    private
    var state:State
}

now i need the actor to be able to yield its stored properties as something that is not isolated to the actor:

struct Server:~Copyable
{
    let plugins:Plugins
    let count:Counters

    let state:Server.State
}

extension ServerLoop
{
    func withServer(_ body:(borrowing Server) async throws -> Void) async rethrows
    {
        let server:Server = ???
    }
}

although the Server could never escape or outlive the ServerLoop, there does not seem to be a way to temporarily pass the Counters to the closure argument without making Counters a class and falling back to reference counting.

indeed, giving up and going back to ARC pretty much describes my experience with ~Copyable. how can i get ~Copyable to do something useful here?

1 Like

This is a common pattern. I imagine handling it with two features that haven't been formally proposed yet: non-escapable types and borrowed references:

struct Server: ~Escapable
{
    let plugins:Plugins
    borrow count: Counters // borrowed reference

    let state:Server.State
}

Maybe there's a clever way to reformulate the problem for use in 5.9. :person_shrugging:

1 Like

i found one really hacky way to get this working:

struct Server:~Copyable
{
    private
    let loop:ServerLoop
    let state:Server.State
}
extension Server
{
    var count:Counters
    {
        _read { yield self.loop.count }
    }
}

the compiler admits it doesn’t know how to compile this.

copy of noncopyable typed value. This is a compiler bug. Please file a bug with a small example of the bug

but i managed to trick it into compiling it anyway using immediately-executed closures.

let n:Int =
{
    (count:borrowing Counters) in

    count.foosBarred.load(ordering: .relaxed)

} (server.count)

who knows what demons i may have unleashed by doing this…

perhaps ~Copyable is not ready for prime time.

1 Like