Law of Exclusivity/memory safety question

The UnsafeMutableBufferPointer that unsafeMutableBuffer produces is an r-value, not an l-value; you can't mutate the pointer itself. (But what you're describing is exactly the ephemeral inout binding from the ownership manifesto, except it was spelled with inout as an introducer there.)

As a keyword alternative to using? Yeah, I think that's viable.

We need some way to mark that this is semantically a coroutine that produces a value by yielding (and then later resuming) rather than simply returning. I suppose that if all these functions produced inout or borrowed values, we could have that be indicated purely by a marker in the type, but I think that would be rather subtle, and more importantly I don't think it's true that we want these functions to only be able to produce inout or borrowed values.

Oh, so this is like a value yield-ed from _read, except that the yield doesn't complete until the lifetime of buffer ends?

That's sort of reminiscent of the way C++ extends the lifetimes of temporaries bound to references (if you think of the yield continuation being run by the temporary's destructor).

It sounds like you're saying there should be a using marker in both the yielding code and the yielded-to code; is that right?

Well, you could yield something inout from one of these like _modify does, but you wouldn't have to, and unsafeMutableBuffer presumably wouldn't want to. So, yes, it would be quite like _read.

However, _read always yields a borrowed value, whereas this could yield an owned value. That doesn't make a difference for a trivial type like UnsafeMutableBufferPointer, of course, but it would affect performance for a non-trivial type, and (eventually) it would affect semantics for a move-only type.

The function has to be marked as special somehow, yeah. On the caller side, I feel that a local declaration that starts one of these scopes should be clear that there's something special about the scope, that it's not just a normal let. But maybe that doesn't have to be explicit if you scope the use within a single statement, like you just immediately pass the yielded value as an argument to a call (and therefore the coroutine call is scoped immediately around the call).

Okay, but notwithstanding what you say next…

That doesn't make a difference for a trivial type like UnsafeMutableBufferPointer , of course, but it would affect performance for a non-trivial type, and (eventually) it would affect semantics for a move-only type.

…I don't see a point in that. An owned value is what's returned by any ordinary call or property access, and if you want to give it a name and keep it alive during a scope you can use an ordinary let binding. I guess the difference would be that the yield-once coroutine effectively allows one to attach a cleanup (continuation) action for the scope of the owned value? Do you have a use-case for that—one that isn't better covered by the fact that move-only types will have destructors?

I was thinking of these things as properties and subscripts, which need no special markings to support a yield. I can imagine wanting function call syntax for yield-once accesses, but am not sure why functions would need a special marking if properties and subscripts don't.

Properties and subscript accessors do need a special marking in order to yield: you have to be defining one of the two special accessors that yield instead of returning. You cannot yield in an ordinary func or getter.

We really don't want to just make an unsafeBuffer property that's treated completely normally in the language. The scoping is critically important, and if it's an ordinary property — even if it's implemented with a coroutine accessor like _read or _modify — the language is not going to understand that it needs to be doing things within the scope, as opposed to just copying the value and exiting the coroutine as soon as possible.

Hmm. That's a fair point: since the goal of this feature would be to say that the value should only be used within the scope, it's not clear why it would ever be useful to provide an owned value that the program could move away and use later. Maybe the return should be implicitly borrowed if not inout.

2 Likes

It's a nice simplification, anyway. That leads me (through several steps) to suggest we can just use borrowed or inout on the return type as the yield marker at the declaration site:

extension Array {
  mutating func unsafeMutableBuffer() 
    -> borrowed UnsafeMutableBufferPointer<Element> 
  {
    yield ...
  }
}