[swift-users] Understanding pass-by-value
Brent Royal-Gordon
brent at architechies.com
Fri Nov 4 08:42:24 CDT 2016
> On Nov 4, 2016, at 5:59 AM, Ryan Lovelett via swift-users <swift-users at swift.org> wrote:
>
> struct Foo {
> init(from buffer: Data) {
> bar = integer(withBytes: Array(buffer[4..<6]))
> baz = integer(withBytes: Array(buffer[6..<8]))
> ...
> }
>
> let d = Data(count: Int(3e+8))
> let f = Foo(from: d)
>
> Did I just make two copies of the `Data`? How would I investigate this
> to understand it?
Do you mean, "did I make two copies of the `Data`, one in a top-level variable named `d` and the other in a parameter named `buffer`"?
If so, then answer is "Yes, but…"
A value type like `Data` can't really hold variable-sized data like the bytes in a `Data` object. Instead, the bytes are stored in a separate object, and `Data` manages that with copy-on-write semantics. In other words, there are two copies of the `Data` instance itself, but they share a single copy of the bytes they're storing.
To illustrate more clearly, after this line:
let d = Data(count: Int(3e+8))
You have something like this:
| ...stack frame for top level... | +-------------------------------+
| Data instance (d) | --------------> | ...3e+8 bytes of data... |
| | +-------------------------------+
And then once you execute the call on this line:
let f = Foo(from: d)
You have this:
| ...stack frame for top level... | +-------------------------------+
| Data instance (d) | --------------> | ...3e+8 bytes of data... |
| | +-------------------------------+
| ...stack frame for Foo(from:) | ^
| Data instance (buffer) | ---------------------------------+
If `Foo(from:)` were to copy `buffer` and then mutate the copy, the bytes would be copied before the mutation occurred. But since neither `f` nor `buffer` is mutated in this code (indeed, both are in immutable variables!), that never happens here.
> I _think_ that if I made it `inout` then it would not make a copy but
> now the data buffer is mutable. I want it to be clear I'm not mutating
> the buffer inside the initializer but I also want to be sure I'm not
> making a copy of the buffer either.
That's implementation-specific. Notionally, an `inout` variable is essentially passed in by value, modified as a copy, returned to the caller, and then assigned back to the original value. In some cases that's basically what actually ends up happening. But Swift tries to optimize `inout` behavior into directly mutating the original variable, and it's often successful.
--
Brent Royal-Gordon
Architechies
More information about the swift-users
mailing list