Reading a computed property in a method consumes self but only in some cases?

Hi folks!

I'm learning Swift and I've been playing around with borrowing and consuming, and I ran into something I don't quite understand.

It seems that in a generic struct that has a stored property with the generic type, reading a computed property inside a borrowing method consumes self. So eg. this contrived but minimal example won't compile:

struct Gleb<Base> {
  let base: Base
  var computed: Int { 666 }
  borrowing func doSomething() -> Int { computed }
}
/whee/bla.swift:59:18: error: 'self' is borrowed and cannot be consumed
  borrowing func doSomething() -> Int { computed }
                 ^
/whee/bla.swift:59:41: note: consumed here
  borrowing func doSomething() -> Int { computed }
                                        ^

Changing the property to { borrowing get { 666 } } doesn't help.

All of these compile just fine:

class ClassyGleb<Base> {
  let base: Base
  var computed: Int { 666 }
  borrowing func doSomething() -> Int { computed }

  init(base: Base) {
    self.base = base
  }
}

struct Baseless {
  var computed: Int { 666 }
  borrowing func doSomething() -> Int { computed }
}

struct Funcy<Base> {
  let base: Base
  var computed: Int { 666 }
  func _computed() -> Int { computed }
  borrowing func doSomething() -> Int { _computed()  }
}

What exactly is going on here? Why does storing a generically typed base: Base throw a wrench into things if you read computed properties from a borrowing method, but only if it's a struct? I've tried reading SE-0377 and SE-0390, but either haven't successfully understood them or this behavior isn't covered in them.

❯ swift -version

swift-driver version: 1.90.11.1 Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-macosx14.0
3 Likes

There's clearly something I don't understand about ownership mechanics and how they relate to computed properties, because this happens with protocols too:

struct Example {
  protocol Proto {
    var count: Int { borrowing get }
  }
  
  func takeProto(_ p: borrowing some Proto) -> Int {
    p.count
  }
}

This fails to compile because 'p' is borrowed and cannot be consumed even though I again tried with an explicit borrowing get.

This doesn't seem to fit any of the cases listed as a consuming operation in SE-0390:

  • assigning a value to a new let or var binding, or setting an existing variable or property to the binding
  • passing an argument to a consuming parameter of a function
  • passing an argument to an init parameter that is not explicitly borrowing
  • invoking a consuming method on a value, or accessing a property of the value through a consuming get or consuming set accessor
  • explicitly consuming a value with the consume operator
  • return-ing a value

That looks like a bug to me. This should work.

Ah, well that would make sense. Any idea if my original example should also work? Intuitively both of these cases feel like they should be fine

Yeah, I would expect all of your examples to compile. It compiles if the types themselves are ~Copyable, so it should also compile if the types are Copyable but the parameter bindings have implicit copying suppressed with borrowing/consuming modifiers.

1 Like
2 Likes