Ownership specifiers are rather awkward to work with

the following might describe a lot of programming tasks:

  1. create an instance of TypeA that does some work on some buffers, then
  2. transfer the buffers from TypeA to a second type, TypeB that does some more work on those buffers.
struct TypeA:~Copyable
{
    private
    var a:[A]
    private
    var b:[B]
    private
    var c:[C]
    private
    var d:[D]

    private 
    let aStuff:Stuff
    ...
}
extension TypeA
{
    public consuming
    func finalizeMesh() -> TypeB
}
public
struct TypeB:~Copyable
{
    public
    let a:[A]
    public
    let b:[B]
    public
    let vertices:[Vertex]

    init(
        a:consuming [A], 
        b:consuming [B],
        c:borrowing [C],
        d:borrowing [D])
}

and yet i keep finding myself declaring reams of local bindings to try and pass the data from TypeA to TypeB.

extension TypeA
{
    public consuming
    func finalizeMesh() -> TypeB
    {
        let a:[A] = self.a
        let b:[B] = self.b
        let c:[C] = self.c
        let d:[D] = self.d

        _ = consume self

        return .init(a: a, b: b, c: c, d: d)
    }
}
extension TypeB
{
    init(
        a:consuming [A], 
        b:consuming [B],
        c:borrowing [C],
        d:borrowing [D])
    {
        var a:[A] = a
        var b:[B] = b
        let c:[C] = c
        let d:[D] = d

        for a:A in a { ... }
        for b:B in b { ... }
        for a:A in a { ... }
        ...
    }
}

to me, this feels like a lot of ceremony just to accomplish a simple goal: transfer ownership of some buffers to TypeB so it can perform some post-processing.

1 Like

Would this help - bundle the buffers into some sort of container and pass the container instead to the next stage?

struct TypeA: ~Copyable {
    struct Work {
        var a:[A]
        var b:[B]
        var c:[C]
        var d:[D]
    }

    private output: Work
    ...
}
extension TypeA {
    public consuming func finalizeMesh() -> TypeB {
        let output = self.output
        _ = consume self

        return .init (input: output)
    }
}
extension TypeB {
    ...

    init (input: consuming TypeA.Buffers) {
        var input = input
        ...
    }
}

this essentially just shifts the problem from having spurious bindings to having spurious types that exist solely for the purpose of evading ownership errors.

ideally, i would want to be able to just do

extension TypeA
{
    public consuming
    func finalizeMesh() -> TypeB
    {
        let a:[A] = consume self.a
        let b:[B] = consume self.b

        return .init(a: a, b: b, c: self.c, d: self.d)
    }
}

and then from TypeB, to do something along the lines of

extension TypeB
{
    init(
        a:__owned var [A], 
        b:__owned var [B],
        c:__shared [C],
        d:__shared [D])
    {

where __owned var transfers ownership but doesn’t actually make a and b non-moveable in the body of the function.

1 Like

That should be possible once partial consumption support is implementation, or even:

  return .init(a: consume self.a, b: consume self.b, c: self.c, d: self.d)

with or without the explicit consumes.

3 Likes

yes that would be absolutely lovely!

as for the init side, i’m not too annoyed with having to rebind consuming parameters, since you would have to rebind an equivalent __owned parameter anyway to make it mutable. but i find that borrowing just isn’t very useful to me when it isn’t attached to a real noncopyable parameter.

most of the time when i use borrowing, i am really just using it as a non-underscored version of __shared that comes with a lot of annoying move checking that i don’t want. is __shared still the recommended way for expressing “i want this argument passed +0, but i don’t want any move checking applied inside the function body”?