TL;DR: I'm looking for rules of thumb about the circumstances when optimized Swift code will copy a (nontrivial) struct/enum value.
Coming from C++ as I do, I'm used to thinking about the overhead of passing parameters by value vs by reference, or of copying a value vs using a move constructor. In Swift these things tend to be implementation details of the optimizer, but part of my brain still wonders whether I'm doing things efficiently.
I'd like to know if there are rules of thumb for what will cause a struct/enum value to be copied, so I can design appropriately.
Here are some things that I believe to be true:
- Class and actor instances are of course never copied. You have to explicitly create a new instance. (Same goes for
~Copyablevalues, for different reasons.) - Tiny types like
IntandDoubleare passed by value, usually in a register. - Small-ish types that fit in "a few" registers, like structs/enums/tuples of a few primitive fields, are passed by value in multiple registers. (I know this is ISA-specific but I think it's true in both x86-64 and ARM.)
- Bigger types are passed by reference. This is safe because Swift guarantees that a mutable reference never coexists with read-only references. An
inoutargument is modified in place by the callee, without copying. - Similarly,
selfis passed by reference to a method, and amutatingmethod modifies its receiver in place. - I'm less sure about return values. In a C/C++ ABI small return values are usually returned in one or perhaps "a few" registers, and larger return values have space reserved by the caller, which passes a reference to that space as a hidden parameter. Not sure if Swift does that too. That can result in a copy if the struct exists as a stored value somewhere else and gets returned to the caller. (Unless the compiler is able to inline the function, maybe.)
- Explicitly assigning a value to another variable copies it (unless the compiler can optimize it away.)
- Properties with custom setters, and subscripts with setters, seem like the major place where implicit copies happen. If I write
foo.string += "x", andFoo.stringis a property with a custom getter/setter, Swift will call the getter and copy the String, then mutate the string, then call the setter to copy it back. Same thing witharray[6].mutatingMethod()-- it calls the Array subscript to copy the value, invokesmutatingMethod(), then copies the result back.
Is this accurate? Are there more situations to consider?