[Pitch] `@noImplicitCopy` attribute for local variables and function parameters

Hi everyone! Another feature we're working on to provide tools for predictable performance is a way to fully suppress implicit copying on specific variables, and let you manually copy them where necessary. We hope this will provide a wider-scoped, but still localized, tool for controlling the compiler's copy behavior than things like the [take operator]. It's also a good building block on the way to move-only types that we think will still be valuable even when we have those, since we expect that a lot of code will still be generally fine with implicitly-copyable types and only want to take explicit control in sensitive parts of the codebase. I've put up a draft proposal for you all to start reading here:

A few things in particular I'm looking for feedback on:

  • The name @noImplicitCopy says what it does, I guess, but is undeniably clunky.
  • Once we take away the ability to implicitly copy or retain a value, we need to account for what operations on the value are borrowing and consuming on the value. I started a list for both in the proposal, but I undoubtedly forgot some cases.

Thank you for your feedback!

9 Likes

Suggestion: @copying(explicit). And maybe there could be a @copying(forbidden) if it makes sense to forbidden copy at all, even using the copy operator.

5 Likes

Our thinking for this attribute is that it should remain non-transitive, meaning that you can pass your copy-restricted bindings to other functions that may still copy them, since to do otherwise would require an expensive audit of existing code to annotate what functions internally do not copy their arguments, and that at that level of effort, adopting move-only types might be a better investment. Do you think @copying(forbidden) would still be useful as a local constraint?

1 Like

I see the ergonomic reason for this, but it's somewhat unfortunate that @noImplicitCopy really only means "no implicit copy visible directly within this scope"—it seems like the attribute... overpromises what it actually protects from. It would still be local-only, for instance, to require potentially copying uses of a @noImplicitCopy binding to be annotated with copy, but perhaps this would require copy in too many places in practice...

Somewhat relatedly, would it potentially be desirable for an explicit borrow parameter to imply @noImplicitCopy? It seems like you'd do this when you specifically want to avoid copying, so it might be appropriate to ensure you don't actually end up copying the value internally. But this would introduce a difference between implicitly borrow parameters and explicitly borrow parameters which might be undesirable.

Before having read the document, my naive impression regarding a @noImplicitCopy binding was simply that one would have to either explicitly copy or explicitly move take.

Having read it now, I see that what's perhaps even more salient to the feature than "no implicit copy" is the "eager move" ("eager take"?) aspect. Without attempting to nail down a perfect non-clunky name, I'd suggest that this should be foregrounded in how the feature is described and taught.

2 Likes

I'm not sure if I understand correctly, but I have a small concern on this feature.

@noImplicitCopy let x = 42
func foo(_ value: take Int) {}
func bar(_ value: Int) {}

// not Error
foo(copy x)
bar(x)

However, after the author of foo changes the function into borrow, it still compiles without error.

// modified function
func foo(_ value: borrow Int) {}

// still not Error
foo(copy x)
bar(x)

Here, no implicit copy happens by default, but the copy operator remains even though it's no longer necessary and actually wasteful. I believe this type of change happens to some extent. In order to avoid this kind of mistake, should there be some warning for non-sense copy usages?

IIUC, copy doesn’t guarantee that there’s some sort of memory-level copy that actually occurs—the optimizer is still free to eliminate unnecessary copies. So at least in trivial cases it seems like this shouldn’t be wasteful.

1 Like

You're right, but I think warning here would still be helpful.

1 Like