| :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 |