The code compiles but I believe it shouldn't (please correct me if I'm wrong). SE-0366 requires that a variable captured by an @escaping closure shouldn't be used as operand of consume operator. It doesn't mention non-escaping closure explicitly, which implies it's OK. However, if fn is considered as non-escaping closure, consume foo shouldn't be allowed in above case.
Non-escaping is a property of the function type, and not something we infer from the usage of the value. The type of fn is an escaping function type here.
This is surprising, unless it's a typo. I always thought the only way to define a function as escaping is in a parameter position in a function's declaration.
As I understand above code, the reason it compiles is different and correct, but I might be wrong: Foo is a copyable struct, so when the non-escaping closure stored in fn captures it, it's copied then, i.e. when the closure is defined. This happens before the current scope gives up ownership and is thus okay.
The foo variable is consumed, but that does not affect the captured copy inside the closure.
If you opt out of copy ability by declaring it as struct Foo: ~Copyable { ... } the compiler indeed gives an error.
As said, I understand the closure is indeed non-escaping, but I believe that does not play a role here. I don't fully get how consume works with copyable types, though.
No, my statement is correct. All function types are @escapingexcept those that are written in parameter position. A function in parameter position is non-escaping by default, unless you write @escaping.
Eg:
func f(_ fn1: () -> (), // non-escaping: parameter position
_ fn2: @escaping () -> (), // escaping: explicit attribute
_ fn3: Array<() -> ()>) // escaping: not parameter position
-> () -> () // escaping: not parameter position
let fn4 = fn1 // non-escaping: inferred type
let fn5: () -> () = { } // escaping: not parameter position
}
Thank you, @Slava_Pestov!
Now that you mentioned it, I dimly remember when the default was changed, but I didn't read the proposal and to be honest never thought much about (non-)escaping outside of function parameters.
It actually makes sense that they're generally escaping if not in a parameter-position (especially if they're stored in a container, like the array example you gave).
Is the rest of what I wrote still accurate then? As I understand it, while SE-0366 does indeed say "A binding with static lifetime [...] cannot be captured by an @escaping closure", this does not apply to @rayx's example since Foo is copyable.
I believe this is hard to decipher from the proposal, but I think that the term "binding with static lifetime" refers to the actual value of a thing, in this case the blob of memory that holds foo. foo itself, being a constant, is the "reference to [that]" the proposal mentions.
Since Foo is copyable, the foo inside the closure references (in the end) a different "binding with static lifetime", the value gets copied-on-write (well, more like copied-on-need).
So if the foo outside of the closure gets consumed that's fine: It just means this reference cannot be used anymore after the consume statement in this scope (and its "binding with static lifetime" is destroyed).
If Foo is ~Copyable, however, bothfoos (the captured one in the closure as well as the one outside) reference the same "binding with static lifetime" (after all, it cannot be copied). This is the case the proposal mentions: The escaping closure captures a "binding with static lifetime" and the compiler throws an error.
Is this a correct understanding of what happens and why the original code not showing an error is not a bug?
I think out an experiment to verify your understanding. The result seems to indicate you're right. The key setup is consume x in the closure. The fact that it compiles proves that the closure uses a copy of x.
func test() {
let x = "abc"
let fn: () -> Void = {
print(x)
_ = consume x // !
}
_ = consume x
fn()
}
So my origianal code isn't a good test. I have modified the code in #83282 (I filed it a while back to track the issue) to define x as a mutable variable. It would also be interesting to see the result of the above code if x is mutable. Unfortunately compiler crashes rather than produce a diaganostic (that's what I expected). I filed #83562 to track the crash.
It's well documented that doSth(x:) takes a implicitly borrowing parameter, but the above code shows that it's OK to consume the parameter (assuming it isn't a bug, I guess it's because an implict copying happens at the consume line in doSth(x:)). So, the fact that consume works in doSth(x:) (or in fn closure) isn't sufficient to prove that the function (or closure) gets an independent copy in the beginning. I also considered using isKnownUniquelyReferenced(_:) to test it, but it doesn't work with immutable variable. It would help if ownership proposals could clarify what's the ownership of an immutable variable captured by an escaping closure (perhaps implicit borrowing too?).
Not quite; it takes a borrow of an implicit copy. The implicit copy might be optimized out in the caller but it’s not the same as a borrow of the original value because in the latter case you get a non-instantaneous access and all that entails from an exclusivity standpoint.
Thanks for the correction. It all makes sense now. I now think @Gero's understanding is correct. Closure fn uses an implicit copy of x.
BTW, I think SE-0377 fails to mention it explicitly. Below are related texts in it. None suggest that an implicit copy is made in above case.
Other functions default to borrowing their parameters, since we have found this to be more efficient in most situations.
A value would need to be implicitly copied if:
a consuming operation is applied to a borrowing binding, or
a consuming operation is applied to a consuming binding after it has already been consumed, or while a borrowing or mutating operation is simultaneously being performed on the same binding
where consuming, borrowing, and mutating operations are as described for values of noncopyable type in SE-0390.
SE-0390 does have the following, but it applies to non-copyable only:
The following operations are borrowing:
...
Invoking a borrowing method on a value, or a method which is not annotated as any of borrowing, consuming or mutating, borrows the self parameter for the duration of the callee's execution.
My above code also exposes another bug in nighlty build. I'll update #83393 to include it.
func test(x: consuming String) {
doSth(x: x) // Bug: a consuming param shouldn't be implicitly copied
_ = consume x
}
func doSth(x: String) {
print(x)
_ = consume x
}
I wonder why there should be a bug here. If we remove the two distracting consume x , the code should clearly compile.
To my understanding, whether the compiler makes a copy when passing a copyable to a (implicitly) borrowing function is just an implementation detail, and there should not be any easily observed consequences. I think it should be OK to just assume no copy is made.
The situations where an (implicit) copy is required are already listed in the proposal, as you’ve quoted:
Did you notice x is a consuming parameter? IMO the entire purpose of consuming and borrowing modifier is to disable implicit copy. See the following in SE-0377:
Applying one of these modifiers to a parameter causes that parameter binding to no longer be implicitly copyable, and potential copies need to be marked with the new copy x operator.
I think the proposal list them as background. It helps readers to understand that copy operator should be used in this situations.
Of course I know its consuming. Not sure how you interpret the lines, but I’m just saying passing a consuming parameter to a function with a borrowing parameter does not require it being copied.
It’s crystal clear for me that it’s always OK to perform a borrowing operation on a valid consuming binding.
I don’t think this is true. There’re clues in the same section, like this one:
The following operations are borrowing:
Passing an argument to a func or subscript parameter that does not have an ownership modifier, or an argument to any func, subscript, or init parameter which is explicitly marked borrow. The argument is borrowed for the duration of the callee's execution.
Only parameters of copyable types can “not have an ownership modifier”, the definitions of borrowing/consuming operations in SE-0390 definitely cover copyable values.
But doSth doesn't take an explicit borrowing parameter. That's the key difference. Based on earlier explanation by @Slava_Pestov, I think what happens is an implicit copy is made by test and doSth borrows that copy. In my understanding this isn't allowed by SE-0377. That said, I just tried a modified version of the code in Swift 5.9 and it compiled, so I don't know for sure. I think it's a corner case because the implicit copy occurs at function boundary, so it's up to how the feature's designers think about it.
Deleted
[quote="CrystDragon, post:13, topic:81279"]
I don’t think this is true. There’re clues in the same section, like this one:
The following operations are borrowing:
Passing an argument to a func or subscript parameter that does not have an ownership modifier, or an argument to any func, subscript, or init parameter which is explicitly marked borrow. The argument is borrowed for the duration of the callee's execution.
Only parameters of copyable types can “not have an ownership modifier”, the definitions of borrowing/consuming operations in SE-0390 definitely cover copyable values.
[/quote]
The text you quoted comes from SE-0390, which is about non-copyable. While consuming and borrowing parameters and non-copyable values have many similar behaviors. I doubt if the text you quoted is applicable in this case. Non-copyable can't be copied, so it has to be borrowed. consuming and borrowing parameters, however, can be copied (using copy operator).
OK, I think you're right, because I find this example in SE-0377:
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`
}
This is confusing because:
While the authors of SE-0377 suggested there was no implicit copy made in bar(z: x) call on a conceptual level, it actually happens in implementation (see this thread)
It's still not clear to me if an immutable variable is implicitly copied when it's captured by an escaping closure (this is also @Gero's original question). I suspect an implicit copy is made, but it's not obvious by reading SE-0377. In the pre-owership time, we think passing a parameter to a function and capturing an immutable variable in an escaping closure work the same way - they both pass a copy of value to callee and escaping closure, respectively. If they work differently now, I think it's important to clarify it.
func test1() {
let foo = Foo()
let fn: () -> Void = { foo.dump() } // Q: is an implicit copy of foo made here?
_ = consume foo
fn()
}
EDIT: regarding my question in item 2, I find the following in SE-0390:
Variables captured by escaping closures thus behave like class properties; immutable captures are treated as always borrowed both inside the closure body and in the capture's original context.
IIUC that means foo is borrowed by the closure. Whether an implicit copy is made in caller depends on the context. If there is no code to consume foo, no implicit copy is made.
func test() {
let x = "abc"
let fn: () -> Void = { print(x) }
print(x) // no need to make implicit copy
fn()
}
otherwise an implicit copy is made.
func test() {
let x = "abc"
let fn: () -> Void = { print(x) }
_ = consume x // need to make implicit copy
fn()
}
I hope this is the right way to understand it. My only question now is, since Swift is capable of creating implicit copy when there are conflicting requests for immutable value, does the following statement in SE-0366 apply to only mutable variables?
A binding with static lifetime also must satisfy the following requirements:
it cannot be captured by an @escaping closure or nested function