You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/_docs/reference/experimental/cc.md
+36-53Lines changed: 36 additions & 53 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -43,12 +43,12 @@ followed by `^`. We'll see that this turns the parameter into a _capability_ who
43
43
44
44
If we now try to define the problematic value `later`, we get a static error:
45
45
```
46
-
|val later = usingLogFile { f => () => f.write(0) } // error
47
-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
48
-
|Found: (f: java.io.FileOutputStream^?) ->? () ->{f} Unit
49
-
|Required: java.io.FileOutputStream^ => () ->? Unit
46
+
|val later = usingLogFile { f => () => f.write(0) }
47
+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
48
+
|Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit
49
+
|Required: java.io.FileOutputStream^ => () ->'s3 Unit
50
50
|
51
-
|Note that capability f cannot be included in outer capture set ?.
51
+
|Note that capability f cannot be included in outer capture set 's3.
52
52
```
53
53
In this case, it was easy to see that the `logFile` capability escapes in the closure passed to `usingLogFile`. But capture checking also works for more complex cases.
54
54
For instance, capture checking is able to distinguish between the following safe code:
@@ -60,11 +60,11 @@ val xs = usingLogFile { f =>
60
60
and the following unsafe one:
61
61
```scala
62
62
valxs= usingLogFile { f =>
63
-
LazyList(1, 2, 3).map { x => f.write(x); x * x }
63
+
LzyList(1, 2, 3).map { x => f.write(x); x * x }
64
64
}
65
65
```
66
66
An error would be issued in the second case, but not the first one (this assumes a capture-aware
67
-
formulation of `LazyList` which we will present later in this page).
67
+
formulation `LzyList` of lazily evaluated lists, which we will present later in this page).
68
68
69
69
It turns out that capture checking has very broad applications. Besides the various
70
70
try-with-resources patterns, it can also be a key part to the solutions of many other long standing problems in programming languages. Among them:
@@ -89,12 +89,12 @@ The capture checker extension introduces a new kind of types and it enforces som
89
89
Capture checking is done in terms of _capturing types_ of the form
90
90
`T^{c₁, ..., cᵢ}`. Here `T` is a type, and `{c₁, ..., cᵢ}` is a _capture set_ consisting of references to capabilities `c₁, ..., cᵢ`.
91
91
92
-
A _capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability
92
+
An _object capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability
93
93
must be a capturing type with a non-empty capture set. We also say that
94
94
variables that are capabilities are _tracked_.
95
95
96
96
In a sense, every
97
-
capability gets its authority from some other, more sweeping capability which it captures. The most sweeping capability, from which ultimately all others are derived is written `cap`. We call it the _universal capability_.
97
+
capability gets its authority from some other, more sweeping capability which it captures. The recursion stops with a _universal capability_, written `cap`, from which all other capabilities are ultimately derived.
98
98
If `T` is a type, then `T^` is a shorthand for `T^{cap}`, meaning `T` can capture arbitrary capabilities.
99
99
100
100
Here is an example:
@@ -107,8 +107,8 @@ class Logger(fs: FileSystem^):
107
107
deftest(fs: FileSystem^) =
108
108
vall:Logger^{fs} =Logger(fs)
109
109
l.log("hello world!")
110
-
valxs:LazyList[Int]^{l} =
111
-
LazyList.from(1)
110
+
valxs:LzyList[Int]^{l} =
111
+
LzyList.from(1)
112
112
.map { i =>
113
113
l.log(s"computing elem # $i")
114
114
i * i
@@ -120,9 +120,9 @@ and retained as a field in class `Logger`. Hence, the local variable `l` has typ
120
120
`Logger^{fs}`: it is a `Logger` which retains the `fs` capability.
121
121
122
122
The second variable defined in `test` is `xs`, a lazy list that is obtained from
123
-
`LazyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy,
123
+
`LzyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy,
124
124
it needs to retain the reference to the logger `l` for its computations. Hence, the
125
-
type of the list is `LazyList[Int]^{l}`. On the other hand, since `xs` only logs but does
125
+
type of the list is `LzyList[Int]^{l}`. On the other hand, since `xs` only logs but does
126
126
not do other file operations, it retains the `fs` capability only indirectly. That's why
127
127
`fs` does not show up in the capture set of `xs`.
128
128
@@ -140,7 +140,7 @@ This type is a shorthand for `(A -> B)^{c, d}`, i.e. the function type `A -> B`
140
140
The impure function type `A => B` is treated as an alias for `A ->{cap} B`. That is, impure functions are functions that can capture anything.
141
141
142
142
A capture annotation `^` binds more strongly than a function arrow. So
143
-
`A -> B^{c}` is read as `A -> (B^{c})`.
143
+
`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^{cap})`.
144
144
145
145
Analogous conventions apply to context function types. `A ?=> B` is an impure context function, with `A ?-> B` as its pure complement.
146
146
@@ -205,15 +205,15 @@ we have
205
205
The set consisting of the root capability `{cap}` covers every other capture set. This is
206
206
a consequence of the fact that, ultimately, every capability is created from `cap`.
207
207
208
-
**Example 2.** Consider again the FileSystem/Logger example from before. `LazyList[Int]` is a proper subtype of `LazyList[Int]^{l}`. So if the `test` method in that example
209
-
was declared with a result type `LazyList[Int]`, we'd get a type error. Here is the error message:
208
+
**Example 2.** Consider again the FileSystem/Logger example from before. `LzyList[Int]` is a proper subtype of `LzyList[Int]^{l}`. So if the `test` method in that example
209
+
was declared with a result type `LzyList[Int]`, we'd get a type error. Here is the error message:
Why does it say `LazyList[Int]^{fs}` and not `LazyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening.
216
+
Why does it say `LzyList[Int]^{fs}` and not `LzyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening.
217
217
This widening is called _avoidance_; it is not specific to capture checking but applies to all variable references in Scala types.
218
218
219
219
## Capability Classes
@@ -363,39 +363,37 @@ This principle plays an important part in making capture checking concise and pr
363
363
364
364
## Escape Checking
365
365
366
-
Some capture sets are restricted so that
367
-
they are not allowed to contain the universal capability.
368
366
369
-
Specifically, if a capturing type is an instance of a type variable, that capturing type
370
-
is not allowed to carry the universal capability `cap`. There's a connection to tunnelling here.
371
-
The capture set of a type has to be present in the environment when a type is instantiated from
372
-
a type variable. But `cap` is not itself available as a global entity in the environment. Hence,
373
-
an error should result.
367
+
Capabilities follow the usual scoping discipline, which means that capture sets
368
+
can contain only capabilities that are visible at the point where the set is defined.
374
369
375
-
We can now reconstruct how this principle produced the error in the introductory example, where
370
+
We now reconstruct how this principle produced the error in the introductory example, where
| val later = usingLogFile { f => () => f.write(0) }
383
-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
384
-
|The expression's type () => Unit is not allowed to capture the root capability `cap`.
385
-
|This usually means that a capability persists longer than its allowed lifetime.
378
+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
379
+
| Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit
380
+
| Required: java.io.FileOutputStream^ => () ->'s3 Unit
381
+
|
382
+
| Note that capability f cannot be included in outer capture set 's3.
386
383
```
387
384
This error message was produced by the following logic:
388
385
389
386
- The `f` parameter has type `FileOutputStream^`, which makes it a capability.
390
387
- Therefore, the type of the expression `() => f.write(0)` is `() ->{f} Unit`.
391
388
- This makes the type of the whole closure passed to `usingLogFile` the dependent function type
392
-
`(f: FileOutputStream^) -> () ->{f} Unit`.
389
+
`(f: FileOutputStream^'s1) ->'s2 () ->{f} Unit`,
390
+
for some as yet uncomputed capture sets `'s1` and `'s2`.
393
391
- The expected type of the closure is a simple, parametric, impure function type `FileOutputStream^ => T`,
394
392
for some instantiation of the type variable `T`.
395
-
-The smallest supertype of the closure's dependent function type that is a parametric function type is
396
-
`FileOutputStream^ => () ->{cap} Unit`
397
-
-Hence, the type variable `T` is instantiated to `() ->{cap} Unit`, or abbreviated `() => Unit`,
398
-
which causes the error.
393
+
-Matching with the found type, `T` must have the shape `() ->'s3 Unit`, for
394
+
some capture set `'s3` defined at the level of value `later`.
395
+
-That capture set cannot include the capability `f` since `f` is locally bound.
396
+
This causes the error.
399
397
400
398
An analogous restriction applies to the type of a mutable variable.
401
399
Another way one could try to undermine capture checking would be to
@@ -408,23 +406,8 @@ usingLogFile { f =>
408
406
}
409
407
loophole()
410
408
```
411
-
But this will not compile either, since mutable variables cannot have universal capture sets.
412
-
413
-
One also needs to prevent returning or assigning a closure with a local capability in an argument of a parametric type. For instance, here is a
414
-
slightly more refined attack:
415
-
```scala
416
-
classCell[+A](x: A)
417
-
valsneaky= usingLogFile { f =>Cell(() => f.write(0)) }
418
-
sneaky.x()
419
-
```
420
-
At the point where the `Cell` is created, the capture set of the argument is `f`, which
421
-
is OK. But at the point of use, it is `cap` (because `f` is no longer in scope), which causes again an error:
422
-
```
423
-
| sneaky.x()
424
-
| ^^^^^^^^
425
-
|The expression's type () => Unit is not allowed to capture the root capability `cap`.
426
-
|This usually means that a capability persists longer than its allowed lifetime.
427
-
```
409
+
But this will not compile either, since the capture set of the mutable variable `loophole` cannot refer to variable `f`, which is not visible
410
+
where `loophole` is defined.
428
411
429
412
Looking at object graphs, we observe a monotonicity property: The capture set of an object `x` covers the capture sets of all objects reachable through `x`. This property is reflected in the type system by the following _monotonicity rule_:
0 commit comments