Borrowing from a field prevents access to self

I've run into a really annoying problem involving non-Escapable types that have inout access to their source. If I create one of these on a struct member, it makes the entire struct inaccessible, not just the field that's being accessed.

Here's a simplified example:

import Foundation
struct Foo {
    var data = Data(repeating: 0, count: 100)
    var n = 0

    mutating func modify() -> Int {
        let span = data.mutableSpan //ERROR: Overlapping accesses to 'self', but modification requires exclusive access
        n += span.count
        return n
    }
}

It took me a while to figure out the problem. In the modify method, the variable span has inout access to data, so data can't be accessed while it exists. That's a good thing.

But because data is embedded inside self, the compiler considers self to be inout-accessed, so other fields like n can't be accessed either!

The advice is to "consider making a copy", but in my real code it's not a Span it's a non-copyable type, so that's out. I could move that value to a temporary, but that has the side effect of consuming self, which is even worse!

In my real code the struct member is a type of database cursor and the non-escapable value I get from it is a row, so it's like

  func nextRow() {
    guard let row = try _stmt.next() else {return nil}
    ...
  }

Basically as long as row exists, _stmt is off-limits (which is great, because moving the cursor invalidates the values row points to) but I can't access self either! The only workaround seems to be to copy everything I need out of row, then use that to operate on self.

2 Likes

Yeah, this is something we'd like to look into improving in the future. Since we do support partial consumption, you can break self into pieces explicitly, modify them independently, then reassemble self when you're done:

import Foundation
struct Foo {
    var data = Data(repeating: 0, count: 100)
    var n = 0

    mutating func modify() -> Int {
        var (data, n) = (self.data, self.n)

        let span = data.mutableSpan //ERROR: Overlapping accesses to 'self', but modification requires exclusive access
        n += span.count

        self = .init(data: data, n: n)
        return n
    }
}
4 Likes