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/capture-checking/scoped-caps.md
+43-34Lines changed: 43 additions & 34 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,7 +44,8 @@ is interpreted as having an existentially bound `cap` in the result, like this:
44
44
```
45
45
The same rules hold for the other kinds of function arrows, `=>`, `?->`, and `?=>`. So `cap` can in this case absorb the function parameter `x` since `x` is locally bound in the function result.
46
46
47
-
However, the expansion of `cap` into an existentially bound variable only applies to functions that use the dependent function style syntax, with explicitly named parameters. Parametric functions such as `A => B^` or `(A₁, ..., Aₖ) -> B^` don't bind the `cap` in their return types in an existential quantifier. For instance, the function
47
+
However, the expansion of `cap` into an existentially bound variable only applies to functions that use the dependent function style syntax, with explicitly named parameters. Parametric functions such as `A => B^` or
48
+
`(A₁, ..., Aₖ) -> B^` don't bind the `cap` in their return types in an existential quantifier. For instance, the function
48
49
```scala
49
50
(x: A) ->B->C^
50
51
```
@@ -60,7 +61,7 @@ In other words, existential quantifiers are only inserted in results of function
60
61
- Therefore
61
62
`(x: T) -> A => B` expands to `(x: T) -> ∃c.(A ->{c} B)`.
In both cases, the type system prevents the handle from escaping the callback. Rust achieves this by requiring `'a` to be contained within the closure's scope. Scala achieves it by checking that `file`'s level (tied to `withFile`) cannot flow into `escaped`'s level (at `main`).
157
+
139
158
The key analogies are:
140
-
-**Levels ≈ Lifetimes**: Both represent "how long something is valid"
141
-
-**Containment ≈ Outlives**: Rust's `'a: 'b` (a outlives b) corresponds to Scala's level containment check (but inverted: inner scopes are contained in outer ones)
142
-
-**Escape prevention**: Both reject code where a reference/capability would outlive its scope
159
+
-**Capability name ≈ Lifetime parameter**: Where Rust writes `&'a T`, Scala writes `T^{x}`. The capability `x` carries its lifetime implicitly via its level.
160
+
-**Capture set ≈ Lifetime bound**: A capture set `{x, y}` bounds the lifetime of a value to be no longer than the shortest-lived capability it contains.
161
+
-**Level containment ≈ Outlives**: Rust's `'a: 'b` (a outlives b) corresponds to Scala's level check (inner scopes are contained in outer ones).
-**Explicit vs. implicit**: Rust lifetimes are often written explicitly (`&'a T`). Scala capture checking levels are computed automatically from the program structure.
165
+
-**Explicit vs. implicit**: Rust lifetimes are explicit parameters (`&'a T`). Scala levels are computed automatically from program structure: you name the capability, not the lifetime.
147
166
148
167
## Charging Captures to Enclosing Scopes
149
168
150
-
When a capability is used, it must flow into the `cap`s of all enclosing scopes. This process is
151
-
called _charging_ the capability to the environment.
169
+
When a capability is used, it must be checked for compatibility with the capture-set constraints of
170
+
all enclosing scopes. This process is called _charging_ the capability to the environment.
152
171
153
172
```scala
154
173
defouter(fs: FileSystem^):Unit=
@@ -158,7 +177,7 @@ def outer(fs: FileSystem^): Unit =
158
177
```
159
178
160
179
When the capture checker sees `fs.read()`, it verifies that `fs` can flow into each enclosing scope:
161
-
1. The immediately enclosing closure `() => fs.read()` must have`fs` in its capture set ✓
180
+
1. The immediately enclosing closure `() => fs.read()` must permit`fs` in its capture set ✓
162
181
2. The enclosing method `inner` must account for `fs` (it does, via its result type) ✓
163
182
3. The enclosing method `outer` must account for `fs` (it does, via its parameter) ✓
164
183
@@ -171,24 +190,14 @@ def process(fs: FileSystem^): Unit =
171
190
172
191
The closure is declared pure (`() -> Unit`), meaning its `cap` is the empty set. The capability `fs` cannot flow into an empty set, so this is rejected.
173
192
174
-
## Visibility and Widening
193
+
###Visibility and Widening
175
194
176
195
When capabilities flow outward to enclosing scopes, they must remain visible. A local capability cannot appear in a type outside its defining scope. In such cases, the capture set is _widened_ to the smallest visible super capture set:
177
196
178
197
```scala
179
-
deftest(fs: FileSystem^):Logger^{fs}=
198
+
deftest(fs: FileSystem^):Logger^=
180
199
vallocalLogger=Logger(fs)
181
200
localLogger // Type widens from Logger^{localLogger} to Logger^{fs}
182
201
```
183
202
184
-
Here, `localLogger` cannot appear in the result type because it's a local variable. The capture set `{localLogger}` widens to `{fs}`, which covers it (since `localLogger` captures `fs`) and is visible outside `test`. In effect, `fs` flows into the result's `cap` instead of `localLogger`.
185
-
186
-
However, widening cannot always find a valid target:
187
-
188
-
```scala
189
-
deftest(fs: FileSystem^): () ->Unit=
190
-
vallocalLogger=Logger(fs)
191
-
() => localLogger.log("hello") // Error: cannot widen to empty set
192
-
```
193
-
194
-
The closure captures `localLogger`, but the return type `() -> Unit` has an empty capture set. There's no visible capability that covers `localLogger` and can flow into an empty set, so the checker rejects this code.
203
+
Here, `localLogger` cannot appear in the result type because it's a local variable. The capture set `{localLogger}` widens to `{fs}`, which covers it (since `localLogger` captures `fs`) and is visible outside `test`. In effect, `fs` flows into the result's `cap` instead of `localLogger`.
0 commit comments