Does anyone know if the result that foo1 fails to compile and foo2 succeeds is as expected? (BTW, I'm using Swift 6.1.2, because the feature is broken on nightly build).
func foo1(x: consuming String) -> String {
let y = x // error: 'x' consumed more than once
return x
}
func foo2(x: consuming String) -> String {
doSomething(x: x) // no error
return x
}
func doSomething(x: String) -> String {
return x
}
According to SE-0377, a consuming parameter is considered as implicitly copied if consuming operations are applied to the parameter multiple times or ... (this is my rephrasing and I skip the part that's irrelevant in the example). That seems to explain why foo2 is OK, but I can't see why let y = x is considered as consuming operation.
I also combined foo1 and foo2 and it compiles. So it appears that, if a consuming parameter is transformed by a function, assigning its output isn't considered as implicit copying.
func foo3(x: consuming String) -> String {
let y = doSomething(x: x) // no error
return x
}
I'd appreciate if anyone could elaborate on it a bit.
The parameter of doSomething is implicitly borrowing, that's why foo2 is valid. Inside foo2, x is consumed only once (returned), passing it to doSomething is not a consuming operation.
where consuming , borrowing , and mutating operations are as described for values of noncopyable type in SE-0390.
You should check that proposal if you haven't.
As for foo3, y and x are distinct values when we are talking about ownerships. Whether y is an actual copy of x or not is only meaningful inside the implementation of doSomething, from the local perspective of foo3, they are independent of each other.
Marking a parameter with eitherconsumingorborrowing disables implicit copying.
SE-0377 gives the following example with a borrowing parameter, which applies equally to consuming and addresses exactly your question:
func foo(x: borrowing String) {
let y = x // ERROR: attempt to copy `x`
bar(z: x) // OK, invoking `bar(z:)` does not require copying `x`
}
func bar(z: String) {
let w = z // OK, z is implicitly copyable here
}
It also says, in the sentence immediately preceding that example:
The constraint on implicit copies only affects the parameter binding itself.
So as to your last example, inside the body of foo3, there can be implicit copying but it isn't an error. This is because x: consuming String only disables implicit copying of x itself.
Thank @xwu and @CrystDragon. @xwu's answer is exactly what I was looking for. I missed that part in the proposal.
If you don't mind, I have a further question. Although I know that parameter of bar is implicitly borrowing, I don't understand what the word "copying" in this comment exactly means.
bar(z: x) // OK, invoking `bar(z:)` does not require copying `x`
In my understanding x's value in foo stack frame is still copied to bar's stack frame. It's just that the copy can't be mutated. So, I guess what comment really means is that it's guaranteed that x's buffer won't be COW'ed in foo's body (not including bodies of function calls within foo). Is my understanding correct?
I ask because I'm trying to understand why return value and assignment are considered as implicit copy. I don't think it can be explained by the usual value semantics, because otherwise bar(z: x) would be implicit copy too.
A copy is an identical, interchangeable representation of a value. When you write let w = z in bar(z:), you have made a copy because afterwards in that scope you can use both w and z independently. You would not need to make a copy if you allowed the assignment to consumez, meaning that afterwards you can only use w. Consider the following variations:
func a(x: consuming String) {
let y = x // OK (no implicit copy; 'x' is consumed once).
// You can use 'y' here, but no more using 'x'!
}
func b(x: consuming String) {
let y = x
let z = x // Not OK ('x' is consumed more than once).
}
func c(x: consuming String) {
let y = copy x
let z = x // OK ('x' is consumed once).
}
func d(x: borrowing String) {
let y = x // Not OK (no implicit copy; 'x' is borrowed and cannot be consumed).
}
func e(x: borrowing String) {
let y = copy x // OK.
}
func f(x: String) {
let y = x // OK (implicit copy).
}
These are notional copies that don't mean that any memory is being copied at runtime; stack frames and copy-on-write buffers are implementation details that are not pertinent to this notion of copying.
From the caller side, the return value is not considered storage—you cannot actually write let y = consume doSomething(x: x). I've clarified my earlier post so that it doesn't suggest that you're implicitly copying the return value. My earlier point was merely that you are allowed inside foo3 to implicitly copy any copyable value other than x. For example:
func g(x: borrowing String) -> String {
let y = copy x
let z = y // OK.
return z // OK.
}
I'd like to note this phrase might not be accurate, the accurate phrase is "return value and assignment are considered as consuming operations". Performing a consuming operation does not necessarily result in making a copy. Making copies is only needed in certain conditions (for example, a consuming value being consumed more than once, or a borrowing value being used in a consuming operation...).
In situations where a copy is needed:
For Copyable parameters, if they are not explicitly marked with ownership modifiers, the compiler will automatically insert copy operations (implicit copy)
For ~Copyable parameters and Copyable parameters with explicit ownership modifiers, the compiler will emit errors
For Copyable parameters with explicit ownership modifiers, we can use copy operator to solve the issue (explicit copy)
For ~Copyable parameters, there's no general solutions
Now, why are returning and assignment consuming operations? AFAICT, because they are meant to create independent values. I think this decision is natural, from both the compiler and the programmers' perspective.