blob: ea4f5796360f246a10477e10050b45e10cb4677c [file] [log] [blame]
: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