Combined SE-0366 (third review) and SE-0377 (second review): rename `take`/`taking` to `consume`/`consuming`

You have "I don't know what it would mean in function value types" as a bit of a throwaway, but that was one of the reasons we moved inout after the colon in the first place. I don't think we should mess with that even if we can make the declaration "read" a little better to native English speakers. (Though I agree that it's a little clunky, so people should continue to bikeshed the word we use.)

6 Likes

consume/consuming is arguably better than take/taking for readability and understanding.
+1

I also have an affinity towards consumed/consuming over consume/consuming. For any position that isn’t a method modifier my personal inclination is to think about the keyword in terms of its relationship with the type, in which case I think consumed works best. This isn’t a strongly held opinion though by any means.

If this is a concern could it be helped by fixits correcting to the right form?

5 Likes

Yes, it would be very easy to correct the keyword with a fix-it.

1 Like

I only quickly scanned the proposal to find out what will happen to functions named consume as I do write some from time to time and there's one thing that stood out to me in the examples under the answer to my question.

consume x + y // Parses as (consume x) + y

As a reader I would expect this to be consume (x + y) not (consume x) + y.


Is it actually bad to burn the name and introduce a warning for functions called consume? We can simply say it's a warning in Swift 5.x.y, but an error in Swift 6 and require back ticks.

- func consume()
+ func `consume`()

Why do we want to special case these keyword(s)?

3 Likes

This has been already asked and answered:

1 Like

"Consuming" doesn't describe the argument: it describes the relationship between the function and the argument.

I'm not sure this holds true even now though? For example, inout doesn't describe the argument, it describes how the argument interacts with the function.


Also, the use of "consume" and "borrow" for the parameter modifier to my eyes reads very strangly when in the context of point free methods, e.g. this function:

struct Foo {
    consuming func thing(_ bar: consume Bar) -> Baz { ... }
}

has the signature: (consume Foo) -> (consume Bar) -> Baz which doesn't mirror the definition (consuming vs consume). These maybe aren't the strongest arguments, just my 2 cents.

1 Like

Does the not-yet-proposed inout x mean the following?

struct S { var x: Int = 0 }
struct T { var s = S() }

var t = T()
inout nestedProp = t.s.x
nestedProp = 1
t.s.x  // now 1

Yes, that would be the meaning.

Personally, I expect to use these modifiers a lot. Maybe not quite as much as std::move, but they will certainly be important tools when working on real-time graphics in Swift. So I’d be happy with shorter forms in which punctuation played a more prominent role.

Punctuation could also address @John_McCall’s points:

Defining & to mean “operate on this binding” (much like how $ means “operate through this property wrapper”) could make these feel more closely related.

My suggestion is to drop the imperatives entirely and embrace the dichotomy between expressions and declarations:

protocol Renderable {
  func appendDisplayList(to frame: inout Frame) // familiar
}

struct Renderer {
  func render(_ frame: inout(let) Frame) // f.k.a. `borrow Frame`
  func releaseResources(for frame: in Frame) // f.k.a. `consume Frame`
}

class World {
  var entities: [any Renderable]
  func draw(in renderer: Renderer) {
    let frame = Frame()
    for entity in entities {
      entity.appendDisplayList(to: &frame)
    }
    renderer.render(&frame)
    renderer.releaseResources(for: &frame)
    // could also do _ = &frame
}

Do we really need syntax to distinguish between lending and forfeiting at the call site? I don’t think it can be ambiguous as to which operation applies. Anyone familiar with inout parameters can immediately apprehend that &x operates on the binding named x, not the value held within it, and why render(&Frame()) doesn’t work.

2 Likes

Thanks, John. Given that, inout local vars would be really useful. I look forward to the proposal.

To your consum(e|er|ing|ed) naming question: looking at your table, my intuition is that terminology should be aligned along an lvalue/rvalue-flavored distinction betwen “expressions that provide values” (your first column) and “declarations of things that receive values” (the other three columns).

That raises the question of what lhs/rhs combinations like these are allowed and meaningful:

// Are these both allowed? Equivalent?
var x = borrow y
borrow x = y

// Which combinations like these are allowed?
borrow x = consume y
inout x = borrow y
// etc

// Why shouldn’t this be allowed?
consuming x = y

Terminology (including conjugation) should help elucidate all that.

1 Like

I'll second in here; I had the same thought independently. Not sure how I feel about inout(let) though.

1 Like

Another alternative would be to bring back var in parameters:

func appendDisplayList(to frame: inout Frame) // short for `inout var Frame`
func render(_ frame: inout let Frame) // override the implied `var`
func releaseResources(for frame: in var Frame) // no ABI effect; `frame` is mutable within function body
1 Like

Xiaodi wisely points out that this comment I posted in another review is at least as relevant here:

2 Likes

I’ll also follow up from there:

Maybe “lend(ing)” then makes sense at the usage site.

It would make the mental model clearer to me, than trying to use the same/similar word in two places - it gives clarity at the point of reading.

1 Like

This is what I was trying to get across, but your (actual) explanation is much much better. Using a verb makes it clearer (to me) what the function is doing with the instance. The alternative in my head is func ex(arg0: consumes Foo, arg1: borrows Bar) but I think the symmetry of ing is better.

3 Likes

Hello,
This all seems quite good and happy with the direction is going. I just had one question, I hope is alright asking it here, about the behaviour that is not clear to me after reading the proposal.

Do we have to use consume in a parameter on the calling side even if the declaration of the function already has the argument defined as consume? From a first read in the proposal I thought the answer was no, but then I'm not sure. Specially since inout needs & in the other side.

func doStuffUniquely(with value: consume [Int]) {
}

doStuffUniquely(with: consume x) // <-- is this consume needed?

No, for a copyable type you don’t; an implicit copy of the argument will be passed to be consumed if needed. The behavior for non-copyable types will be determined in another proposal.

+1 for "consumed". When people ask questions, the specific words and formulation they use can help to guide their thinking. If I consider a question like:

"why is x being retained? It's a consumed parameter" vs
"why is x being retained? It's a consuming parameter"

I feel that the latter is less clear about what is going on, to the point that it may mislead or cause confusion. It sounds like x is consuming something, when in fact, it is itself being consumed.

It may be a slight initial obstacle for programmers less fluent in English, but inevitably as they work with Swift, they will pick up some amount of English - almost every Swift API, and certainly all of the standard library APIs, are in English, including grammatical nuances such as String.append (mutating) vs String.appending (non-mutating, returns a new String). So it's already the case that less fluent developers would benefit from a basic understanding of English grammar, because they will be able to more quickly understand what APIs do, form expectations about mutating and return types, and select the right API for a given situation, just by looking at a function's base name.

And once they do pick it up, I think they will also benefit from the additional clarity of "consumed", just as more proficient English speakers can.

6 Likes

This line has been aired quite a bit upthread, but since it's still with us…

I think it's a terrible idea to slice (English) grammar so finely as to choose the keywords the grammatical and semantic difference between consumed and consuming in natural language. The Swift context isn't natural language. Make an argument from which spelling you prefer, of course, but arguing from grammar risks blinding you to the preconceptions that developers will actually bring — what's clarifying for you is not guaranteed to be clarifying for others.

I also don't think the discussion has paid enough attention to the difference between understanding the modifier in the calling context vs. the called context. If we're mired in participles and gerundives, then for the caller, the function is consuming its argument; for the callee, the function has consumed its parameter. Which of these the modifier should mean depends on where you're standing.

This suggestion didn't trigger much discussion, but I think it's an excellent compromise. It doesn't bother too much with the overall grammar of the function call (read as a pseudo-sentence), and it works just as well for the caller as for the callee.

consumes Foo states directly that something does something consumption-like to the Foo at the time of the call, and just bypasses the excursion into English grammar.

Really, isn't the goal here to be clear-as-a-keyword, not clear-as-English (which, in regard to the superficial syntax of its grammar, isn't what you could call "clear" anyway)?

3 Likes

Thank you everyone for participating in this review! SE-0366 is accepted with the name consume:

And SE-0377 is accepted with the names consuming and borrowing:

Holly Borla
Review Manager

12 Likes