File: MutationModel.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 (302 lines) | stat: -rw-r--r-- 9,705 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
:orphan:

.. raw:: html

    <style>
    table.docutils td,
    table.docutils th {
      border: 1px solid #aaa;
    }
    </style>


==================================
Immutability and Read-Only Methods
==================================

:Abstract: Swift programmers can already express the concept of
           read-only properties and subscripts, and can express their
           intention to write on a function parameter.  However, the
           model is incomplete, which currently leads to the compiler
           to accept (and silently drop) mutations made by methods of
           these read-only entities.  This proposal completes the
           model, and additionally allows the user to declare truly
           immutable data.

The Problem
===========

Consider::

  class Window {

    var title: String { // title is not writable
      get {
        return somethingComputed()
      }
    }
  }

  var w = Window()
  w.title += " (parenthesized remark)"

What do we do with this?  Since ``+=`` has an ``inout`` first
argument, we detect this situation statically (hopefully one day we'll
have a better error message):

.. code-block:: swift-console

 <REPL Input>:1:9: error: expression does not type-check
 w.title += " (parenthesized remark)"
 ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~

Great.  Now what about this? [#append]_ ::

  w.title.append(" (fool the compiler)")

Today, we allow it, but since there's no way to implement the
write-back onto ``w.title``, the changes are silently dropped.

Unsatisfying Approaches
=======================

We considered three alternatives to the current proposal, none of
which were considered satisfactory:

1. Ban method calls on read-only properties of value type
2. Ban read-only properties of value type
3. Status quo: silently drop the effects of some method calls

For rationales explaining why these approaches were rejected, please
refer to earlier versions of this document.

Proposed Solution
=================

Terminology
-----------

Classes and generic parameters that conform to a protocol attributed
``@class_protocol`` are called **reference types**.  All other types
are **value types**.

Mutating and Read-Only Methods
------------------------------

A method attributed with ``inout`` is considered **mutating**.
Otherwise, it is considered **read-only**.

.. parsed-literal::

   struct Number {
     init(x: Int) { name = x.toString() }

     func getValue() {              // read-only method
       return Int(name)
     }
     **mutating** func increment() {  // mutating method
       name = (Int(name)+1).toString()
     }
     var name: String
   }

The implicit ``self`` parameter of a struct or enum method is semantically an
``inout`` parameter if and only if the method is attributed with
``mutating``.  Read-only methods do not "write back" onto their target
objects.

A program that applies the ``mutating`` to a method of a
class--or of a protocol attributed with ``@class_protocol``--is
ill-formed.  [Note: it is logically consistent to think of all methods
of classes as read-only, even though they may in fact modify instance
variables, because they never "write back" onto the source reference.]

Mutating Operations
-------------------

The following are considered **mutating operations** on an lvalue

1. Assignment to the lvalue
2. Taking its address

Remember that the following operations all take an lvalue's address
implicitly:

* passing it to a mutating method::

    var x = Number(42)
    x.increment()         // mutating operation

* passing it to a function attributed with ``@assignment``::

    var y = 31
    y += 3                // mutating operation

* assigning to a subscript or property (including an instance
  variable) of a value type::

    x._i = 3             // mutating operation
    var z: Array<Int> = [1000]
    z[0] = 2             // mutating operation

Binding for Rvalues
-------------------

Just as ``var`` declares a name for an lvalue, ``let`` now gives a
name to an rvalue:

.. parsed-literal::

   var clay = 42
   **let** stone = clay + 100 // stone can now be used as an rvalue

The grammar rules for ``let`` are identical to those for ``var``.

Properties and Subscripts
-------------------------

A subscript or property access expression is an rvalue if

* the property or subscript has no ``set`` clause
* the target of the property or subscript expression is an rvalue of
  value type

For example, consider this extension to our ``Number`` struct:

.. parsed-literal::

  extension Number {
    var readOnlyValue: Int { return getValue()  }

    var writableValue: Int {
      get {
       return getValue()
      }
      **set(x)** {
        name = x.toString()
      }
    }

    subscript(n: Int) -> String { return name }
    subscript(n: String) -> Int {
      get {
        return 42
      }
      **set(x)** {
        name = x.toString()
      }
    }
  }

Also imagine we have a class called ``CNumber`` defined exactly the
same way as ``Number`` (except that it's a class).  Then, the
following table holds:

+----------------------+----------------------------------+------------------------+
|          Declaration:|::                                |                        |
|                      |                                  |::                      |
|Expression            |   var x = Number(42)  // this    |                        |
|                      |   var x = CNumber(42) // or this |  let x = Number(42)    |
|                      |   let x = CNumber(42) // or this |                        |
+======================+==================================+========================+
| ``x.readOnlyValue``  |**rvalue** (no ``set`` clause)    |**rvalue** (target is an|
|                      |                                  |rvalue of value type)   |
|                      |                                  |                        |
+----------------------+                                  |                        |
| ``x[3]``             |                                  |                        |
|                      |                                  |                        |
|                      |                                  |                        |
+----------------------+----------------------------------+                        |
| ``x.writeableValue`` |**lvalue** (has ``set`` clause)   |                        |
|                      |                                  |                        |
+----------------------+                                  |                        |
| ``x["tree"]``        |                                  |                        |
|                      |                                  |                        |
+----------------------+----------------------------------+                        |
| ``x.name``           |**lvalue** (instance variables    |                        |
|                      |implicitly have a ``set``         |                        |
|                      |clause)                           |                        |
+----------------------+----------------------------------+------------------------+

The Big Rule
-------------

.. Error:: A program that applies a mutating operation to an rvalue is ill-formed
   :class: warning

For example:

.. parsed-literal::

   clay = 43           // OK; a var is always assignable
   **stone =** clay \* 1000 // **Error:** stone is an rvalue

   swap(&clay, **&stone**) // **Error:** 'stone' is an rvalue; can't take its address

   **stone +=** 3          // **Error:** += is declared inout, @assignment and thus
                       // implicitly takes the address of 'stone'

   **let** x = Number(42)  // x is an rvalue
   x.getValue()        // ok, read-only method
   x.increment()       // **Error:** calling mutating method on rvalue
   x.readOnlyValue     // ok, read-only property
   x.writableValue     // ok, there's no assignment to writableValue
   x.writableValue++   // **Error:** assigning into a property of an immutable value

Non-``inout`` Function Parameters are RValues
----------------------------------------------

A function that performs a mutating operation on a parameter is
ill-formed unless that parameter was marked with ``inout``.  A
method that performs a mutating operation on ``self`` is ill-formed
unless the method is attributed with ``mutating``:

.. parsed-literal::

  func f(_ x: Int, y: inout Int) {
    y = x         // ok, y is an inout parameter
    x = y         // **Error:** function parameter 'x' is immutable
  }

Protocols and Constraints
-------------------------

When a protocol declares a property or ``subscript`` requirement, a
``{ get }`` or ``{ get set }`` clause is always required.

.. parsed-literal::

   protocol Bitset {
     var count: Int { **get** }
     var intValue: Int { **get set** }
     subscript(bitIndex: Int) -> Bool { **get set** }
   }

Where a ``{ get set }`` clause appears, the corresponding expression
on a type that conforms to the protocol must be an lvalue or the
program is ill-formed:

.. parsed-literal::

  struct BS {
    var count: Int    // ok; an lvalue or an rvalue is fine

    var intValue : Int {
      get {
        return 3
      }
      set {             // ok, lvalue required and has a set clause
        ignore(value)
      }
    }

    subscript(i: Int) -> Bool {
      return true   // **Error:** needs a 'set' clause to yield an lvalue
    }
  }

-----------------

.. [#append] String will acquire an ``append(other: String)`` method as part of the
             formatting plan, but this scenario applies equally to any
             method of a value type