File: ValueSemantics.rst

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (379 lines) | stat: -rw-r--r-- 12,987 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
:orphan:

.. _ValueSemantics:

==========================
 Value Semantics in Swift
==========================

:Abstract: Swift is the first language to take Generic Programming
 seriously that also has both value and reference types.  The
 (sometimes subtle) differences between the behaviors of value and
 reference types create unique challenges for generic programs that we
 have not yet addressed.  This paper surveys related problems
 and explores some possible solutions.


Definitions
===========

I propose the following definitions of "value semantics" and
"reference semantics."

Value Semantics
---------------

For a type with value semantics, variable initialization, assignment,
and argument-passing (hereafter, "the big three operations") each
create an *independently modifiable copy* of the source value that is
*interchangeable with the source*. [#interchange]_

If ``T`` has value semantics, the ``f``\ s below are all equivalent::

  func f1() -> T {
     var x : T
     return x
  }

  func f2() -> T {
     var x : T
     var y = x
     return y  // a copy of x is equivalent to x
  }

  func f2a() -> T {
     var x : T
     var y : T
     y = x
     return y  // a copy of x is equivalent to x
  }

  func f3() -> T {
     var x : T
     var y = x
     y.mutate() // a copy of x is modifiable
     return x   // without affecting x
  }

  func f3a() -> T {
     var x : T
     var y : T
     y = x;
     y.mutate() // a copy of x is modifiable
     return x   // without affecting x
  }

  func g(_ x : T) { x.mutate() }

  func f4() -> T {
     var x : T
     g(x)         // when x is passed by-value the copy
     return x     // is modifiable without affecting x
  }


Reference Semantics
-------------------

Values of a type with reference semantics are only accessible
indirectly, via a reference.  Although swift tries to hide this fact
for simplicity, for the purpose of this discussion it is important to
note that there are always *two* values in play: the value of the
reference itself and that of the object being referred to (a.k.a. the
target).

The programmer thinks of the target's value as primary, but it is
never used as a variable initializer, assigned, or passed as a
function argument.  Conversely, the reference itself has full value
semantics, but the user never sees or names its type.  The programmer
expects that copies of a reference share a target object, so
modifications made via one copy are visible as side-effects through
the others.

If ``T`` has reference semantics, the ``f``\ s below are all
equivalent::

  func f1(_ x: T) {
     x.mutate()
     return x
  }

  func f2(_ x: T) -> T {
     var y = x
     y.mutate()  // mutation through a copy of x
     return x    // is visible through x
  }

  func f2a(_ x: T) -> T {
     var y : T
     y = x
     y.mutate()  // mutation through a copy of x
     return x    // is visible through x
  }

  func g(_ x : T) { x.mutate() }

  func f3(_ x: T) -> T {
     g(x)        // when x is passed to a function, mutation
     return x    // through the parameter is visible through x
  }

The Role of Mutation
--------------------

It's worth noting that in the absence of mutation, value semantics and
reference semantics are indistinguishable.  You can easily prove that
to yourself by striking the calls to ``mutate()`` in each of the
previous examples, and seeing that the equivalences hold for any type.
In fact, the fundamental difference between reference and value
semantics is that **value semantics never creates multiple paths to
the same mutable state**. [#cow]_

.. Admonition:: ``struct`` vs ``class``

   Although ``struct``\ s were designed to support value semantics and
   ``class``\ es were designed to support reference semantics, it would
   be wrong to assume that they are always used that way.  As noted
   earlier, in the absence of mutation, value semantics and reference
   semantics are indistinguishable.  Therefore, any immutable ``class``
   trivially has value semantics (*and* reference semantics).

   Second, it's easy to implement a ``struct`` with reference semantics:
   simply keep the primary value in a ``class`` and refer to it through
   an instance variable.  So, one cannot assume that a ``struct`` type
   has value semantics.  ``Array`` could be seen (depending on how you
   view its value) as an example of a reference-semantics ``struct``
   from the standard library.

The Problem With Generics
=========================

The classic Liskov principle says the semantics of operations on
``Duck``\ 's subtypes need to be consistent with those on ``Duck`` itself,
so that functions operating on ``Duck``\ s still "work" when passed a
``Mallard``.  More generally, for a function to make meaningful
guarantees, the semantics of its sub-operations need to be consistent
regardless of the actual argument types passed.

The type of an argument passed by-value to an ordinary function is
fully constrained, so the "big three" have knowable semantics.  The
type of an ordinary argument passed by-reference is constrained by
subtype polymorphism, where a (usually implicit) contract between
base- and sub-types can dictate consistency.

However, the situation is different for functions with arguments of
protocol or parameterized type.  In the absence of specific
constraints to the contrary, the semantics of the big three can vary.

Example
-------

For example, there's an algorithm called ``cycle_length`` that
measures the length of a cycle of states (e.g. the states of a
pseudo-random number generator).  It needs to make one copy and do
in-place mutation of the state, rather than wholesale value
replacement via assignment, which might be expensive.

Here's a version of cycle_length that works when state is a mutable
value type::

 func cycle_length<State>(
   _ s : State, mutate : ([inout] State) -> ()
 ) -> Int
   requires State : EqualityComparable
 {
     State x = s     // one copy                // 1
     mutate(&x)      // in-place mutation
     Int n = 1
     while x != s {                            // 2
          mutate(&x) // in-place mutation
          ++n
     }
     return n
 }

The reason the above breaks when the state is in a class instance is
that the intended copy in line 1 instead creates a new reference to
the same state, and the comparison in line 2 (regardless of whether we
decide ``!=`` does "identity" or "value" comparison) always succeeds.

You can write a different implementation that only works on clonable
classes:

.. parsed-literal::

 // Various random number generators will implement this interface
 abstract class RandomNumberGenerator
   : Clonable, Equalable
 {
   func nextValue() -> Int
 }

 func cycle_length<State>(
   _ s : State, mutate : ([inout] State) -> ()
 ) -> Int
   requires State : EqualityComparable, **Clonable**
 {
     State x = s\ **.clone()**
     Int n = 1
     while **! x.equal(s)** {
         *etc.*
 }

 RandomNumberGenerator x = new MersenneTwister()
 print(
    cycle_length(x, (x : [inout] RandomNumberGenerator) { x.nextValue() })
 )

You could also redefine the interface so that it works on both values and
clonable classes:

.. parsed-literal::

 func cycle_length<State>(
   _ s : State,
   **next : (x : State) -> State,**
   **equal : (x : [inout] State, y : [inout] State) -> Bool**
 ) -> Int
   requires State : EqualityComparable
 {
     State **x = next(s)**
     Int n = 1
     while **!equal(x, s)** {
          **x = next(x)**
          ++n
     }
     return n
 }

However, this implementation makes O(N) separate copies of the state.
I don't believe there's a reasonable way write this so it works on
clonable classes, non-classes, and avoids the O(N)
copies. [#extension]_

Class Identities are Values
---------------------------

It's important to note that the first implementation of
``cycle_length`` works when the state is the *identity*, rather than
the *contents* of a class instance.  For example, imagine a circular
linked list::

 class Node {
     constructor(Int) { next = this; prev = this }

     // link two circular lists into one big cycle.
     func join(_ otherNode : Node) -> () { ... }

     var next : WeakRef<Node> // identity of next node
     var prev : WeakRef<Node> // identity of previous node
 }

We can measure the length of a cycle in these nodes as follows::

 cycle_length(someNode, (x: [inout] Node){ x = x.next })

This is why so many generic algorithms seem to work on both
``class``\ es and non-``class``\ es: ``class`` *identities*
work just fine as values.

The Role of Moves
=================

Further complicating matters is the fact that the big three operations
can be--and often are--combined in ways that mask the value/reference
distinction.  In fact both of the following must be present in order
to observe a difference in behavior:

1. Use of (one of) the big three operations on an object ``x``,
   creating shared mutable state iff ``x`` is a reference

2. In-place mutation of ``x`` *while a (reference) copy is extant* and
   thus can be observed through the copy iff ``x`` is a reference.

Take, for example, ``swap``, which uses variable initialization and
assignment to exchange two values::

  func swap<T>(_ lhs : [inout] T, rhs : [inout] T)
  {
      var tmp = lhs   // big 3: initialization - ref copy in tmp
      lhs = rhs       // big 3: assignment     - ref copy in lhs
      rhs = tmp       // big 3: assignment     - no ref copies remain
  }

Whether ``T`` is a reference type makes no observable difference in
the behavior of ``swap``.  Why?  Because although ``swap`` makes
reference copies to mutable state, the existence of those copies is
encapsulated within the algorithm, and it makes no in-place mutations.

Any such algorithm can be implemented such that copy operations are
replaced by destructive *moves*, where the source value is not
(necessarily) preserved.  Because movability is a weaker requirement
than copyability, it's reasonable to say that ``swap`` is built on
*moves*, rather than copies, in the same way that C++'s ``std::find``
is built on input iterators rather than on forward iterators.

We could imagine a hypothetical syntax for moving in swift, where
(unlike assignment) the value of the right-hand-side of the ``<-`` is
not necessarily preserved::

  var tmp <- lhs
  lhs <- rhs
  rhs <- tmp

Such operations are safe to use in generic code without regard to the
differences between value- and reference- semantics.  If this syntax
were extended to handle function arguments, it would cover the "big
three" operations::

  f(<-x)

How to Build an Interesting Type with Value Semantics
=====================================================

Suppose we want to build a variable-sized data structure ``X`` with
(mutable) value semantics?  How do we do it?

If we make ``X` a ``class``, we automatically get reference semantics, so
its value must be copied before each mutation, which is tedious and
error-prone.  Its public mutating interface must be in terms of free
functions (not methods), so that the original reference value can be
passed ``[inout]`` and overwritten.  Since there's no user access to the
reference count, we can't determine that we hold the only reference to
the value, so we can't optimize copy-on-write, even in single-threaded
programs.  In multi-threaded programs, where each mutation implies
synchronization on the reference count, the costs are even higher.

If we make the type a ``struct``, you have only two ways to create
variable-sized data:

1. Hold a type with reference semantics as an instance variable.
   Unfortunately, this is really nothing new; we must still implement
   copy-on-write.  We can, however, use methods for mutation in lieu
   of free functions.

2. Use discriminated unions (``union``).  Interestingly, a datatype
   built with ``union`` automatically has value semantics.  However,
   there vocabulary of efficient data structures that can be built
   this way is extremely limited.  For example, while a singly-linked
   list is trivial to implement, an efficient doubly-linked list is
   effectively impossible.

----

.. [#interchange] Technically, copies of objects with value semantics
                  are interchangeable until they're mutated.
                  Thereafter, the copies are interchangeable except
                  insofar as it matters what value type they are
                  *aggregated into*.

.. [#cow] Note that this definition *does* allow for value semantics
              using copy-on-write

.. [#extension] I can think of a language extension that would allow
                this, but it requires creating a protocol for generic
                copying, adding compiler magic to get both classes and
                structs to conform to it, and telling generic
                algorithm and container authors to use that protocol
                instead of ``=``, which IMO is really ugly and
                probably not worth the cost.