Selective control of implicit copying behavior: `take`, `borrow`, and `copy` operators, `@noImplicitCopy`

Sorry, it wasn't clear that I was agreeing with your observation. The example needs to be replaced--probably by a few simpler ones.

One point I should make on "optimizer guarantee" argument though...

There is a critical difference between the two kinds of optimizer guarantees we're talking about. This proposal claims to make strong guarantees that compiler can find the optimal points where a variable's static lifetime ends. The programmer expects the optimizer to be "smart enough to figure it out" despite arbitrary code that may be increasingly complex as the code evolves. That's best done explicitly.

Your counter example talks about whether the optimizer will introduce some pessimization in case the programmer has already created unavoidable copies. "Don't do something crazy dumb" is not something we promote using explicit source controls for. In fact, as the code evolves and becomes more complex, it would be harder for the optimizer to introduce the pessimization.

1 Like

In another topic, the question of whether to adopt __shared led to the conclusion that sometimes the function author just doesn't know whether it will be beneficial. For example, general functions like map(_:) can’t know whether any particular argument benefits from being __shared.

This design proposes take and borrow keywords adorning parameters at the call site, but these keywords only serve as static assertions that the same annotation exists on the corresponding parameter declaration. Can we possibly address the first use case by enhancing the function call ABI to reflect the caller’s ownership preference? For example, a function declared func foo(param: unowned Int) could be called as foo(shared globalInt) or foo(take localInt) (*); at the ABI level, foo would take a hidden parameter that describes whether param was passed shared or take. The downside is increased code size and branchiness within foo, but this could be well worth it for significantly large value types with many members.

(*) Footnote: I still would like to advocate for keywords only at the declaration site, and replacing the call-site keywords with standard library functions move(_:), borrow(_:), etc.

I can think of cases where you might want to have a function that polymorphically ties the convention of a few parameters together (for instance, it'd be cool if map could borrow when the mapped function borrows, takes when the mapped function takes, inouts when the mapped function inouts), but it seems to me like it'd be hard for the callee implementation to take concrete advantage of the benefits of any convention at all if it's entirely up to the caller—every attempt to consume it would need to have a conditional copy in case it was borrowed, and we would need to conditionally destroy it on every exit in case it was taken.

5 Likes

Thanks for the reminder. Based on the discussion between you and Andy, I reworked the motivating example for take to something that wouldn't depend on lifetime barriers to sequence the lifetime of two copies of a variable. I've also incorporated the feedback from the SE-0366 review and the discussion in this thread to revise the proposal:

Here is the new motivating example from the revised proposal:

7 Likes