Law of Exclusivity/memory safety question

Well, it appears what you've done could work in practice. I think the best you can do for now is check that the compiler doesn't violate your assumptions. If the runtime check is too expensive, I could imagine someone adding support for a new compiler diagnostic that does the same check at compile-time: verify that the annotated variable isn't moved or copied.

In theory, the compiler can move values wherever it wants since there's no formal address-pinning beyond the UnsafePointer scope. I don't think that's much of a practical concern, but the compiler could do the following, assuming "instrinsic_move" is its mechanism for relocating storage:

  var selfCopy = intrinsic_move(self)
  var (p, kv) = unsafeMoveMap(destroying: &selfCopy) { $0.tuple }
  self = intrinsic_move(selfCopy)
  defer { p.initialize(to: .init(kv)) }
  yield &kv

In fact, since KeyValuePair is a copyable type, those intrinsic_move's could even be copies--of course the compiler won't do that because it would degrade the quality of CoW.

There was some talk recently about pinning the address of certain class properties--that won't help you, but it's interesting background: Exposing the Memory Locations of Class Instance Variables - #28 by lorentey

With generalized coroutines you do the following:

yield withUnsafeMutablePointer(&self) {
  var kv = $0.move().tuple
  yield &kv
  $0.initialize(to: .init(kv)
}

Hopefully we're not too far away from supporting that, and it does protect you against anything the compiler could do.

It still seems there should be a safe way to borrow transformed values though, I'm just not sure what should look like. You need to promise to writeback your borrowed variable at the end of some scope.

Implicit borrow scopes don't quite work

{
  borrowed let tempSelf = self
  var kv = tempSelf.tuple
  yield &kv
  // implicit borrow scope ends here, moving tempSelf back to self
}

because tempSelf.tuple still forces a copy while the original tempSelf needs to stay live for the writeback move.

I imagine that explicit 'move's could be enforced by the compiler though:

{
  var kv = builtin_move(self).tuple
  yield &kv
  // Compiler checks that 'self' is reinitialized before returning.
  self = builtin_move(KeyValuePair(kv))
}
4 Likes