Skip to content

Commit da1c9e6

Browse files
authored
Merge pull request #126 from dart-lang/immutability
Add initial draft of static immutability
2 parents 078bfd8 + e923165 commit da1c9e6

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
# Shared immutable objects
2+
3+
4+
5+
Status: Draft
6+
7+
This describes a possible solution for:
8+
- [Communication between isolates](https://github.com/dart-lang/language/issues/124)
9+
- [Building immutable collections](https://github.com/dart-lang/language/issues/117)
10+
- [Unwanted mutation of lists in Flutter](https://github.com/dart-lang/sdk/issues/27755)
11+
12+
## Summary
13+
14+
This describes a way to declare classes that produce deeply immutable object
15+
graphs that are shared across isolates.
16+
17+
## Syntax
18+
19+
We add a section to class headers for expressing class and generic constraints,
20+
along with an "immutable" constraint.
21+
22+
```dart
23+
class Value<T> extends Scalar<T> implements Constant is immutable
24+
where T is immutable {
25+
}
26+
```
27+
28+
Mixin declarations may also be marked `immutable`.
29+
30+
Generic method headers may also express generic constraints.
31+
32+
```
33+
foo<S, T, where T is immutable>(Value<T> v) {
34+
35+
}
36+
```
37+
38+
### Alternative syntax 1
39+
40+
Instead of adding constraints, a simpler approach is to add a marker interface
41+
`Immutable`. The property expressed by the constraint `T is immutable` then
42+
becomes expressed by `implements Immutable` in the case of a class, or `T
43+
extends Immutable` in the case of a type variable `T`.
44+
45+
### Alternative syntax 2
46+
47+
Instead of adding general constraints, we could expose a dedicated syntax. For
48+
example, this proposal from @yjbanov.
49+
50+
```dart
51+
data Value<data T> extends Scalar<T> {
52+
}
53+
54+
foo<S, data T>(Value<T> v) {
55+
}
56+
57+
```
58+
59+
60+
## Static checking
61+
A class marked with `immutable` is subject to the following additional static
62+
checks.
63+
64+
- Every field in an immutable class (including any superclass fields) must be
65+
final.
66+
- Every field in an immutable class (including any superclass fields) must have
67+
a static type which is immutable.
68+
- Every other class which implements the interface of an immutable class
69+
(including via extension or mixing in) must also be immutable.
70+
71+
The types `int`, `double`, `bool`, `String`, `Type`, and `Symbol` are considered
72+
immutable.
73+
74+
## Generated methods
75+
76+
We may wish to consider automatically generating hashCode and equality methods
77+
for immutable classes (possibly with caching of hashCode).
78+
79+
We may wish to consider automatically generating functional update methods (or
80+
providing some other form of functional update).
81+
82+
## Allocation of immutable objects
83+
84+
Immutable objects are allocated as usual in an isolate local
85+
nursery. (Alternatively, it might be preferable to maintain a separate isolate
86+
local shared object nursery for allocating only shared objects). However, when
87+
they are tenured, they are tenured to a global heap which is shared by all
88+
isolates in the process, and which is inhabited solely by immutable shared
89+
objects.
90+
91+
The shared object heap cannot have pointers into the isolate local heaps, and so
92+
garbage collection of an isolate local heap does not require coordination with
93+
other isolates.
94+
95+
The isolate local heap can have pointers into the shared global heap, and so
96+
either these must be tracked via write barriers and treated as roots when
97+
collecting the shared global heap, or else collection of the shared global heap
98+
might require cross-isolate coordination.
99+
100+
Tenuring objects into the shared global heap requires locking or pausing
101+
isolates. Bulk reservation of allocation regions could potentially be used to
102+
mitigate this.
103+
104+
Issue: It is possible that a large object may need to be tenured before it has
105+
been fully initialized. This would allow writes into the shared heap. This
106+
should not be problematic semantically since the object cannot be visible in
107+
other isolates prior to initialization, but it may complicate the GC model.
108+
This does not seem deeply problematic - a number of solutions seem plausible.
109+
110+
## Sharing of immutable objects
111+
112+
The SendPort class is extended with a new method `void share<T, where T is
113+
immutable>(T message)` which given a reference to an immutable object graph,
114+
shares that reference with all receivers of the SentPort. Note that the object
115+
is not copied since it and all sub-components of it are in the shared heap.
116+
117+
An object which is shared before it has been tenured will likely need to be
118+
tenured when it is shared.
119+
120+
It should be the case that every object is fully initialized before it can be
121+
shared. The intent of the static checks specified above are to guarantee this.
122+
123+
It should be the case that no object that has been shared can be mutated. The
124+
intent of the static checks specified above are to guarantee this.
125+
126+
## Immutable collections
127+
128+
The following additional immutable classes are added to the core libraries:
129+
`ImmutableList` which implements `List`, `ImmutableMap` which implements `Map`,
130+
and `ImmutableSet` which implements `Set`.
131+
132+
### Collection initialization
133+
Instances of these collections may be allocated and assigned to local variables
134+
in a modifiable state. Mutation operations may be performed on such an instance
135+
up until the first point at which the instance escapes (that is, is captured by
136+
a closure, is assigned to another variable or setter, or is passed as a
137+
parameter). It is a static error if a mutation operation is performed on an
138+
instance of one of these classes:
139+
- at any point not intra-procedurally dominated by the allocation point of the
140+
instance
141+
- at any point where the instance escapes along any path from the allocation
142+
point to the mutation operation.
143+
144+
Instances that are allocated to initialize fields or top level variables are
145+
always initialized in an umodifiable state.
146+
147+
Question: Is this functionality needed? With spread collections, many patterns
148+
will be expressible directly as a literal.
149+
150+
Question: Is this sufficient? The analysis as specified is brittle: you cannot
151+
factor out initialization code into a different scope from the allocation. We
152+
could add type level support for tracking uninitialized instances, but this
153+
raises the footprint of this feature substantially.
154+
155+
Qustion: Should this functionality be extended to user classes?
156+
157+
### Runtime immutability
158+
As with the result of the current `List.unmodifiable` constructor, mutation
159+
operations on an instance of an immutable collection shall throw (except in the
160+
limited cases described in the initialization section above). Note that the
161+
static checks described above prevent mutation operations from being accessed on
162+
an instance of immutable type. However, the immutable collections implement
163+
their mutable interfaces, and hence the mutation operations may be reached by
164+
subsuming into the mutable type.
165+
166+
### Literals
167+
168+
A collection literal which appears in a context where the static type required
169+
by the context is an immutable collection type shall be allocated as an
170+
immutable collection.
171+
172+
```
173+
ImmutableList<int> l = [ 3 ];
174+
```
175+
Question: Do we need additional syntax for the case where a static type context
176+
is not required?
177+
178+
```
179+
var l = ^[3];
180+
```
181+
182+
### Alternative collection approach
183+
184+
Instead of making `ImmutableList` a subtype of `List`, we could make it either
185+
an unrelated type, or a supertype of `List`.
186+
187+
#### `ImmutableList` is a supertype
188+
If `ImmutableList` is a supertype of `List`, then immutability is no longer type
189+
based. If we wish to enforce deep immutability, then there would need to be
190+
runtime checks during initialization, which may be expensive (particularly in
191+
the case of collections). Alternatively, we could simply not enforce deep
192+
immutability statically, and instead dynamically traverse an object grap before
193+
sharing it to check for immutability. This is expensive, but perhaps marginally
194+
less so than copying.
195+
196+
Another downside of this approach is that existing APIs that take `Lists` but
197+
only read them cannot be re-used with an `ImmutableList`. A wrapper can help
198+
with this.
199+
200+
A benefit of this is that changing APIs (especially Flutter APIs) to take
201+
`ImmutableList` as an argument would be non-breaking.
202+
203+
#### `ImmutableList` is an unrelated type
204+
205+
If `ImmutableList` is unrelated to `List`, then we have the same issue with
206+
re-using existing APIs. However, we retain all of the benefits of type based
207+
immutability.
208+
209+
## Immutable functions
210+
211+
There is no way to describe the type of an immutable function. If important, we
212+
could add a type for immutable closures. A function is immutable if every free
213+
variable of the function is immutable.
214+
215+
## Immutable top type
216+
217+
There is no top type for immutable types. It might be useful to have a type
218+
`Immutable`, to express the type of fields of immutable objects which are
219+
intended to hold instances of multiple types which do not otherwise share a
220+
common super-interface.
221+
222+
## Javascript
223+
224+
There are no issues with supporting immutable objects on the web, but the
225+
ability to support communication between isolates is limited. Currently,
226+
isolates are not supported at all in Javascript. If we revisit that, we are
227+
unlikely to be able to support this in full on the web. It is possible that we
228+
may be able to define a subset of immutable objects which can be implemented as
229+
a layer over shared typed data buffers.
230+

0 commit comments

Comments
 (0)