Struggling with basic composition of noncopyable types

here’s something i really thought should be a straightforward composition of noncopyable types, where one buffer is temporarily rebound to another abstraction to avoid copy-on-write:

struct ExampleContext: ~Copyable {
    var buffer: [Int]
}

struct ExampleState: ~Copyable {
    let buffer: [Int]
}
extension ExampleState {
    var context: ExampleContext {
        get {
            .init(buffer: self.buffer)
        }
        _modify {
            var context: ExampleContext = (consume self).context
            defer {
                self = .init(buffer: context.buffer)
            }
            yield &context
        }
    }
}
error: 'self' used after consume
 13 |             .init(buffer: self.buffer)
 14 |         }
 15 |         _modify {
    |         `- error: 'self' used after consume
 16 |             var context: ExampleContext = (consume self).context
    |                                                    `- note: consumed here
 17 |             defer {
    |             `- note: used here
 18 |                 self = .init(buffer: context.buffer)
 19 |             }

what am i doing wrong here?

1 Like

defer is implemented as a closure, and therefore captures any variables named within it (including self). That's why you can't use defer for this. What you need to do is to swap self with some trivially empty state, like Optional.take() does.

This should do the job:

struct ExampleContext: ~Copyable {
    var buffer: [Int]
}

struct ExampleState: ~Copyable {
    let buffer: [Int]
}

extension ExampleState {
    mutating func asContext() -> ExampleContext {
      let context = ExampleContext(buffer: self.buffer)
      self = Self(buffer: [])
      return context
    }
    var context: ExampleContext {
        get {
            .init(buffer: self.buffer)
        }
        _modify {
            var context = self.asContext()
            yield &context
            self = Self(buffer: (consume context).buffer)
        }
    }
}

2 Likes