I meant consuming
on arguments like func foo(_ x: consuming Player)
but you're asking about consuming func foo()
.
So yes, kinda, you're definitely on the right track but it doesn't really remove self
(except if a copy is impossible, see later) from the caller, it merely says 'I will consume ownership of self
'. This may sound a little abstract, let's do examples. Let's start with a class C
, note for classes copying means increasing the ref count.
Let's start with a program that doesn't use consuming
:
class C { func bye() {} }
func HARNESS(_ c: C) -> C { c.bye(); return c; }
if we feed this through the compiler we get
$ echo 'class C { func bye() {} }; func HARNESS(_ c: C) -> C { c.bye(); return c; }' | swiftc -O -emit-assembly -module-name T - | swift demangle | grep -A20 ^T.HARNESS | grep -v .cfi_
T.HARNESS(T.C) -> T.C:
stp x20, x19, [sp, #-32]!
stp x29, x30, [sp, #16]
add x29, sp, #16
mov x20, x0
ldr x8, [x0]
ldr x8, [x8, #80]
blr x8 // <-- CALL to virtual function `bye`
mov x0, x20
ldp x29, x30, [sp, #16]
ldp x20, x19, [sp], #32
b _swift_retain // <-- a retain at the end before return
So when we're calling bye
we don't need to do anything to the ref count because we borrowed (actually @guaranteed
) that from the caller (standard for function parameters). And bye
by default will also just borrowing (actually @guaranteed
) self
.
Before the return however we need to increase the reference count because the default for return values is @owned
.
Now, if we change our little program and make it a consuming func bye()
that means bye
is no longer happy with a @guaranteed
self
, it now wants to consume
it.
class C { consuming func bye() {} }
func HARNESS(_ c: C) -> C { c.bye(); return c; }
gives us
$ echo 'class C { consuming func bye() {} }; func HARNESS(_ c: C) -> C { c.bye(); return c; }' | swiftc -O -emit-assembly -module-name T - | swift demangle | grep -A20 ^T.HARNESS | grep -v .cfi_
T.HARNESS(T.C) -> T.C:
stp x20, x19, [sp, #-32]!
stp x29, x30, [sp, #16]
add x29, sp, #16
mov x20, x0
ldr x8, [x0]
ldr x19, [x8, #80]
bl _swift_retain // <--- retain before call to bye
blr x19 // <--- virtual bye call
mov x0, x20
ldp x29, x30, [sp, #16]
ldp x20, x19, [sp], #32
b _swift_retain // <--- another retain before release
So as you see, the caller doesn't strictly speaking lose access to c
(aka self
in bye
) but it will need to increase the reference count an extra time.
Now, where this really becomes apparent is if we disallow the compiler from making copies/increasing the ref count. Let's consider this slightly modified program (note that C
is now struct C: ~Copyable
):
struct C: ~Copyable { @inline(never) func bye() {} }
func HARNESS(_ c: consuming C) -> C { c.bye(); return c; }
this gives us
$ echo 'struct C: ~Copyable { @inline(never) func bye() {} }; func HARNESS(_ c: consuming C) -> C { c.bye(); return c; }' | swiftc -O -emit-assembly -module-name T - | swift demangle | grep -A3 ^T.HARNESS
T.HARNESS(__owned T.C) -> T.C:
.cfi_startproc
b function signature specialization <Arg[0] = Dead> of T.C.bye() -> ()
.cfi_endproc
shiny! It compiles and the function does nothing but (tail) calling bye
.
But if we now make bye
a consuming func
we'll get:
$ echo 'struct C: ~Copyable { @inline(never) consuming func bye() {} }; func HARNESS(_ c: consuming C) -> C { c.bye(); return c; }' | swiftc -O -emit-assembly -module-name T - | swift demangle | grep -A3 ^T.HARNESS
<stdin>:1:80: error: 'c' consumed more than once
1 | struct C: ~Copyable { @inline(never) consuming func bye() {} }; func HARNESS(_ c: consuming C) -> C { c.bye(); return c; }
| | | `- note: consumed again here
| | `- note: consumed here
| `- error: 'c' consumed more than once
2 |
because the compiler is now unable to add a defensive copy.
I know this is all quite something but I hope it helps.
FWIW, to figure out the calling conventions (@owned
vs @guaranteed
etc) that the compiler picked, I'd recommend looking at the SIL instead of the assembly. E.g.
regular func bye
takes self
(implicit first parameter) as @guaranteed C
$ echo 'class C { func bye() {} }; func HARNESS(_ c: C) -> C { c.bye(); return c; }' | swiftc -O -emit-sil -module-name T - | swift demangle | grep '^sil.*bye'
sil hidden @T.C.bye() -> () : $@convention(method) (@guaranteed C) -> () {
consuming func bye()
takes self
as @owned C
$ echo 'class C { consuming func bye() {} }; func HARNESS(_ c: C) -> C { c.bye(); return c; }' | swiftc -O -emit-sil -module-name T - | swift demangle | grep '^sil.*bye'
sil hidden @T.C.bye() -> () : $@convention(method) (@owned C) -> () {