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
+129-17
Original file line number
Diff line number
Diff line change
@@ -726,41 +726,153 @@ Reach capabilities take the form `x*` where `x` is syntactically a regular capab
726
726
It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources
727
727
`Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`:
728
728
```scala
729
-
classSource[X^]:
730
-
privatevarlisteners:Set[Listener^{X^}] =Set.empty
731
-
defregister(x: Listener^{X^}):Unit=
732
-
listeners += x
729
+
classSource[X^]:
730
+
privatevarlisteners:Set[Listener^{X}] =Set.empty
731
+
defregister(x: Listener^{X}):Unit=
732
+
listeners += x
733
733
734
-
defallListeners:Set[Listener^{X^}] = listeners
734
+
defallListeners:Set[Listener^{X}] = listeners
735
735
```
736
736
The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above
737
-
we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X^`. The `register` method takes a listener of this type
737
+
we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type
738
738
and assigns it to the variable.
739
739
740
-
Capture set variables `X^` are represented as regular type variables with a
741
-
special upper bound `CapSet`. For instance, `Source` could be equivalently
740
+
Capture-set variables `X^` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types.
741
+
742
+
Under the hood, such capture-set variables are represented as regular type variables within the special interval
743
+
`>: CapSet <: CapSet^`.
744
+
For instance, `Source` from above could be equivalently
742
745
defined as follows:
743
746
```scala
744
-
classSource[X<:CapSet^]:
745
-
...
747
+
classSource[X>:CapSet<:CapSet^]:
748
+
...
746
749
```
747
-
`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only purpose is to identify capture set type variables and types. Capture set variables can be inferred like regular type variables. When they should be instantiated explicitly one uses a capturing
748
-
type `CapSet`. For instance:
750
+
`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only
751
+
purpose is to identify type variables which are capture sets. In non-capture-checked
752
+
usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas
753
+
with capture checking enabled, it will take the annotated capture sets into account,
754
+
so that `CapSet^{a}` and `CapSet^{a,b}` are distinct.
755
+
This representation based on `CapSet` is subject to change and
756
+
its direct use is discouraged.
757
+
758
+
Capture-set variables can be inferred like regular type variables. When they should be instantiated
759
+
explicitly one supplies a concrete capture set. For instance:
749
760
```scala
750
-
classAsyncextends caps.Capability
761
+
classAsyncextends caps.Capability
751
762
752
-
deflistener(async: Async):Listener^{async} =???
763
+
deflistener(async: Async):Listener^{async} =???
753
764
754
-
deftest1(async1: Async, others: List[Async]) =
755
-
valsrc=Source[CapSet^{async1, others*}]
756
-
...
765
+
deftest1(async1: Async, others: List[Async]) =
766
+
valsrc=Source[{async1, others*}]
767
+
...
757
768
```
758
769
Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows:
// This is a 'brand" capability to mark what can be mentioned in trusted code
794
+
objecttrustedextends caps.Capability
795
+
796
+
// These capabilities are trusted:
797
+
valtrustedLogger:Logger^{trusted}
798
+
valtrustedChannel:Channel[String]^{trusted}
799
+
// These aren't:
800
+
valuntrustedLogger:Logger^
801
+
valuntrustedChannel:Channel[String]^
802
+
803
+
runSecure: () =>
804
+
trustedLogger.log("Hello from trusted code") // ok
805
+
806
+
runSecure: () =>
807
+
trustedChannel.send("I can send") // ok
808
+
trustedLogger.log(trustedChannel.recv()) // ok
809
+
810
+
runSecure: () =>"I am pure and that's ok"// ok
811
+
812
+
runSecure: () =>
813
+
untrustedLogger.log("I can't be used") // error
814
+
untrustedChannel.send("I can't be used") // error
815
+
```
816
+
The idea is that every capability derived from the marker capability `trusted` (and only those) are eligible to be used in the `block` closure
817
+
passed to `runSecure`. We can enforce this by an explicit capability parameter `C` constraining the possible captures of `block` to the interval `>: {trusted} <: {trusted}`.
818
+
819
+
Note that since capabilities of function types are covariant, we could have equivalently specified `runSecure`'s signature using implicit capture polymorphism to achieve the same behavior:
820
+
```scala
821
+
defrunSecure(block: () ->{trusted} Unit):Unit
822
+
```
823
+
824
+
## Capability Members
825
+
826
+
Just as parametrization by types can be equally expressed with type members, we could
827
+
also define the `Source[X^]` class above could using a _capability member_:
We conclude with a more advanced example, showing how capability members and paths to these members can prevent leakage
848
+
of labels for lexically-delimited control operators:
849
+
```scala
850
+
traitLabelextendsCapability:
851
+
typeFv^// the capability set occurring freely in the `block` passed to `boundary` below.
852
+
853
+
defboundary[T, C^](block: Label{typeFv= {C} } ->{C} T):T=???// ensure free caps of label and block match
854
+
defsuspend[U](label: Label)[D^<: {label.Fv}](handler: () ->{D} U):U=???// may only capture the free capabilities of label
855
+
856
+
deftest=
857
+
valx=1
858
+
boundary: outer =>
859
+
valy=2
860
+
boundary: inner =>
861
+
valz=3
862
+
valw= suspend(outer) {() => z} // ok
863
+
valv= suspend(inner) {() => y} // ok
864
+
valu= suspend(inner): () =>
865
+
suspend(outer) {() => w + v} // ok
866
+
y
867
+
suspend(outer): () =>
868
+
println(inner) // error (would leak the inner label)
869
+
x + y + z
870
+
```
871
+
A key property is that `suspend` (think `shift` from delimited continuations) targeting a specific label (such as `outer`) should not accidentally close over labels from a nested `boundary` (such as `inner`), because they would escape their defining scope this way.
872
+
By leveraging capability polymorphism, capability members, and path-dependent capabilities, we can prevent such leaks from occurring at compile time:
873
+
874
+
*`Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`.
875
+
* When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels.
0 commit comments