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
|