SE-0377 (revision): Make `borrowing` and `consuming` parameters require explicit copying with the `copy` operator

Hello, Swift community.

SE-0377 added the borrowing and consuming parameter modifiers to Swift. It was reviewed and accepted in late 2022, too late to be included in Swift 5.8.

The modifiers added by SE-0377 are semantically critical for non-copyable types, but they also serve an important role as an optimization tool for copyable types. However, the authors of SE-0377 are concerned that it is too easy to accidentally subvert this optimization by introducing implicit copies, and they've submitted a proposal to change the behavior of these modifiers on copyable types by requiring all copies of so-modified parameters to be explicit. This makes these parameters behave roughly like they had non-copyable type. You can see the exact proposed changes here.

Because this is a major change to the behavior of SE-0377, and because SE-0377 has not yet been released, the Language Workgroup has decided to run this proposal as a revision of SE-0377 rather than run a new proposal and leave the SE-0377 proposal in a superseded state reflecting no released version of the Swift tools. SE-0377 is therefore back in review, from now until May 29th, 2023.

Reviews are an important part of the Swift evolution process. All review feedback should be either on this forum thread or, if you would like to keep your feedback private, directly to me as the review manager via the forum messaging feature or email. When contacting the review manager directly, please put "SE-0377" in the subject line.

What goes into a review?

The goal of the review process is to improve the proposal under review through constructive criticism and, eventually, determine the direction of Swift. When writing your review, here are some questions you might want to answer in your review:

  • What is your evaluation of the proposal?
  • Is the problem being addressed significant enough to warrant a change to Swift?
  • Does this proposal fit well with the feel and direction of Swift?
  • If you have used other languages or libraries with a similar feature, how do you feel that this proposal compares to those?
  • How much effort did you put into your review? A glance, a quick reading, or an in-depth study?

More information about the Swift evolution process is available at

https://github.com/apple/swift-evolution/blob/main/process.md

Thank you for contributing to Swift.

John McCall
Review Manager

24 Likes

Love it, great change, +1

1 Like

+1 if you opt into annotating parameters with ownership modifiers then requiring explicit copies makes sense to me

2 Likes

+1 (same thoughts as Franz)

+1, this makes total sense. The explanations offered in the pitch thread for the places we again start allowing implicit copies were convincing to me.

I'd like to suggest one change to the rule about passing borrowing or consuming parameters to other functions. Currently it says:

The value of the parameter may be passed to other functions, or assigned to other variables (if the convention allows), at which point the value may be implicitly copied through those other parameter or variable bindings.

I think this is too permissive, as it would allow things like this:

func foo(x: borrowing String) {
    let y = x // ERROR: attempt to copy `x`
    baz(a: x) // automatically copy? even if `baz`'s parameter is `consuming`?
}

func baz(a: consuming String) {
   ...
}

I think it makes sense to not require a copy when passing to a function parameter in general. But if the parameter in the function you're calling is itself labeled borrowing or consuming, then I would require an explicit copy. This way there's a chain of trust about copies being explicit as long as all your function parameters are borrowing or consuming.

So here's what I'd add to the sentence quoted above:

The value of the parameter may be passed to other functions, or assigned to other variables (if the convention allows), at which point the value may be implicitly copied through those other parameter or variable bindings, unless those other parameters are borrowing or consuming, in which case copies must be explicit.

(I suppose it's possible it already works this way and I just misunderstood.)

7 Likes

+1

It should already work this way, but I can clarify the proposal text. x is noncopyable within foo, and the call baz(a: x) is a use of x within foo, so this would raise an error because x would need to be copied in order to form a consumable argument to the call. If baz wasn't also using an ownership modifier, then the parameter binding a would potentially be implicitly copyable, but that's only within the boundaries of the function; it doesn't spread into callers' function call expressions.

I updated the proposal text to make this more explicit:

9 Likes

Thanks for the clarification.

Though I wonder if the clarified behavior isn't going too far in the opposite direction. I'm not sure disallowing calls to initializers makes that much sense when the parameters haven't explicitly opted in to borrowing or consuming. This implicit ownership behavior feels much more like an optimization detail that shouldn't have any effect on semantics. I think given the callee is allowed to make implicit copies, the caller should be allowed to make implicit copies when calling it too. Otherwise the semantics of what needs a copy before a function call become a game of guessing the calling conventions.

On the other hand, maybe it's fine to guide people into learning the default calling conventions if they're going to use borrowing and consuming. So I'm not too sure.

I agree it's a bit of odd behavior now, and if we weren't stuck with the ABI as it is, maybe we'd make a different call on how unannotated APIs work. To be clear, you aren't banned from calling an initializer, since you can explicitly copy the arguments to it. The initializer ABI is as it is because most initializer arguments are consumed to become part of the new value, so this seems to me to be in the spirit of the proposal, since you do need to have a copy in order to make a new value containing a copy of the component value.

The revision to SE-0377 has been accepted.

I'd like to thank you all for participating in this review.

John McCall
Review Manager

4 Likes