Most of Swift is magical. inout
is only explicit because experience with reference parameters in C++ proved that it was too easy to get logic errors because a variable was unexpectedly passed by l-value reference and modified.
This isnāt quite correct either. Most function parameters are implicitly borrowed
by default; only arguments to setters and initializers are consuming
by default, as a guess for best performance. So bar(arg)
does not consume arg
either.
(As noted elsewhere this ādefaultā is not quite the same since it doesnāt impose the no-implicit-copies restriction, but in terms of ācan you use the thing againā itās definitely the case.)
That's what I thought before this thread started, but if consuming
parameters default to being borrowed when passed to functions with default argument conventions then the lifetime on c
shouldn't have ended when he called copy
on it.
If bar(arg)
isn't translated as bar(consume arg)
, then it must be doing:
bar(borrow arg)
_ = consume arg
Unless this is all just a bug in the implementation of explicitly consuming
parameters, and
func copy<T>(_ value: T) -> T { value }
func foo(a: inout Int, b: consuming Int) {
a = copy(b)
a = b
}
is supposed to work.
EDIT: Wait, this does work! It only errors if copy
isn't generic! This must be a bug. Changing copy
from a function that takes an Int
into a generic function that takes a T
shouldn't change whether b
is consumed when you call it.
EDIT 2: Bug reported, Issue 70659.
Int is trivially-copyable too, which could be interfering with diagnostics (but shouldnāt). String might be a better test in general. But yes, a consuming
parameter is consumed when the function exits if it hasnāt been consumed already.
ā¦before optimizations, anyway. Optimizations are permitted to do a handful of things, including turning a copy+consume into a forward. So looking at generated SIL or LLVM IR or assembly may not reflect the formal semantics of the language, just like how assembly doesnāt have struct types.
(No longer at Apple, but Iāll still say thank you for filing the bug!)
i thought they were __owned
by default?
Thatās a spelling that currently exists for opting into the āimplicitly-consuming
ā behavior, yes. I try not to use undocumented, ambiguously-supported features to describe documented ones. (For example, compiler crashes with __owned
arenāt nearly as high-priority as those with consuming
or with plain initializers or setters.)
The SE-0377 acceptance post also says that initializer parameters default to consuming
.
but thereās an important difference in mutating an __owned
parameter will always copy the buffer, whereas weāre āsupposedā to be able to mutate a consuming
parameter if there are no external references to it.
as far as iām aware, it is not safe (from a performance/memory standpoint) to modify a bare parameter to an init
.
I donāt think thatās contractual either way, just that the parameter is live until the end of the function. But weāre moving into the realm of documentation, and I think the documentation will probably say āif you care about minimizing copies, use borrowing
or consuming
explicitlyā.
but consuming
is way too blunt, as it enforces that the parameter can only be moved once, whereas iām generally more interested in ensuring that the parameter can be moved before the end of the function.
Thatās something to take up in a separate thread in the Evolution section of the forum.
The differences between consuming
and __owned
are:
__owned
always affects symbol names for public ABI when used, whereasconsuming
only affects the symbol name if it changes the default convention.__owned
parameters are locally implicitly copyable, whereasconsuming
parameters locally require explicitcopy x
anywhere they may potentially require a copy.__owned
parameters are locally immutable, whereasconsuming
parameters are locally mutable. In neither case are local mutations exposed in the caller.
There should be no reason to continue using __owned
unless you're stuck with __owned
affecting your symbol names in your ABI. If (the compiler thinks) you need to copy a consuming
parameter, and its type is Copyable
, you can still explicitly copy
it.
The compiler hasn't historically tried to do any lifetime tracking at all with trivial types so there are definitely some known issues with using these modifiers with types like Int
in Swift 5.9. Nightly compilers might be a little better.
if iām understanding correctly, does that mean the guidance here is no longer valid?
if so, i would consider that a massive improvement over what is specified in the ownership vision document. but it contradicts much of what weāve been told about __owned
so far.
tangentially, there is also an unattended compiler bug that causes very hard-to-debug ARC drift on 5.9 toolchains when using consuming
.
From the caller's perspective, working with copyable values, the use of __owned
vs. consuming
by the callee should make no difference whatsoever. You do still have to use consume x
to explicitly end a copyable value's lifetime if you want to forward ownership of it at a specific point.
i was not aware that consume
could be applied to __owned
parameters, but lo and behold it does compile.
struct S
{
var y:[Int]
init(x:[Int])
{
var y:[Int] = consume x
y.append(1)
self.y = y
}
neat!
for curiosityās sake, i wondered if you could also get away with consume
-ing a __shared
parameter:
mutating
func f(x:[Int])
{
var y:[Int] = consume x
y.append(1)
self.y = y
}
the compiler gets confused by that, but then again it should not have compiled in the first place.
$ swift test.swift
test.swift:16:23: error:
'consume' applied to value that the compiler does not support.
This is a compiler bug. Please file a bug with a small example of the bug
var y:[Int] = consume x
Yeah, the internal diagnostic looks like a bug, but you should get a diagnostic trying to consume something whose lifetime can't actually be shortened in practice, like a global, escaped local, or __shared
/borrowing
parameter.
I think you just misread @jrose's comment on shortened lifetimes. He wasn't saying that you can't shorten them yourself, he was saying that the compiler will no longer do so automatically. In particular, I think he was referring to the move to lexical lifetimes.
yes, i was under the false impression that consume
is related to consuming
, because i had seen the āthis is a compiler bugā error many times on parameters without the keyword and thought it was expecting the keyword to be present.