Polymorphic Reachability Types: Tracking Freshness, Aliasing, and Separation in Higher-Order Generic Programs

Fueled by the success of Rust, many programming languages are adding substructural features to their type systems. The promise of tracking properties such as lifetimes and sharing is tremendous, not just for low-level memory management, but also for controlling higher-level resources and capabilities. But so are the difficulties in adapting successful techniques from Rust to higher-level languages, where they need to interact with other advanced features, especially various flavors of functional and type-level abstraction. What would it take to bring full-fidelity reasoning about lifetimes and sharing to mainstream languages? Reachability types are a recent proposal that has shown promise in scaling to higher-order but monomorphic settings, tracking aliasing and separation on top of a substrate inspired by separation logic. However, naive extensions on top of the prior reachability type system λ* with type polymorphism and/or precise reachability polymorphism are unsound, making λ* unsuitable for adoption in real languages. Combining reachability and type polymorphism that is precise, sound, and parametric remains an open challenge. This paper presents a rethinking of the design of reachability tracking and proposes new polymorphic reachability type systems. We introduce a new freshness qualifier to indicate variables whose reachability sets may grow during evaluation steps. The new system tracks variables reachable in a single step and computes transitive closures only when necessary, thus preserving chains of reachability over known variables that can be refined using substitution. These ideas yield the simply-typed λ✦-calculus with precise lightweight, i.e., quantifier-free, reachability polymorphism, and the F<:✦-calculus with bounded parametric polymorphism over types and reachability qualifiers, paving the way for making true tracking of lifetimes and sharing practical for mainstream languages. We prove type soundness and the preservation of separation property in Coq. We discuss various applications (e.g., safe capability programming), possible effect system extensions, and compare our system with Scala’s capture types.


INTRODUCTION
Type systems based on ownership and borrowing are seeing increasing practical adoption, most prominently for ensuring memory safety in comparatively low-level "systems languages" such as Rust [Matsakis and Klock 2014].But what about higher-level languages, specifically those that rely to a larger degree on functional and type-level abstraction (e.g., Scala and OCaml)?
Tracking substructural properties such as lifetimes and sharing in the type system holds great promise, not only for low-level memory management, but also for managing a variety of other resources (e.g., files, network sockets, access tokens, mutex locks, etc.), for tracking effects (e.g., via capabilities for exceptions, algebraic effects, continuations, callbacks via async/await, etc.), as well as for compiler optimizations (e.g., fine-grained dependency analysis [Bračevac et al. 2023], Authors' addresses: Guannan Wei, Department of Computer Science, Purdue University, West Lafayette, IN, USA, guannanwei@purdue.edu;Oliver Bračevac, Department of Computer Science, Purdue University, West Lafayette, IN, USA, bracevac@purdue.edu;Songlin Jia, Department of Computer Science, Purdue University, West Lafayette, IN, USA, jia137@purdue.edu;Yuyan Bao, School of Computer and Cyber Sciences, Augusta University, Augusta, GA, USA, yubao@augusta.edu;Tiark Rompf, Department of Computer Science, Purdue University, West Lafayette, IN, USA, tiark@purdue.edu.safe destructive updates, etc.).Therefore, it is no surprise that several mainstream languages are moving in this direction with experimental proposals backed by serious engineering efforts, which are to a large degree inspired by the success of Rust (e.g., Linear Haskell [Bernardy et al. 2018] and Scala Capture Types [Boruch-Gruszecki et al. 2021;Odersky et al. 2021Odersky et al. , 2022]]).
However, these proposals all focus on relatively narrow substructural properties rather than attempting to model lifetimes and sharing with similar generality as Rust's ownership and borrowing approach.For example, the Linear Haskell extension specifically tracks multiplicity of uses, and the Scala Capture Types extension specifically targets effect capabilities.Of course, this is neither neglect, nor coincidence, but the observable effect of an underlying hard problem: ownership type systems [Clarke et al. 2013[Clarke et al. , 1998;;Noble et al. 1998] that would enable tracking more sophisticated lifetime properties traditionally rely on strict heap invariants (selectively relaxed via borrowing [Hogg 1991]) that are difficult to enforce in the presence of pervasive functional and type-level abstraction (see Figure 1 for an example).
Reachability Types.Reachability types [Bao et al. 2021] are a recently introduced close cousin to ownership types and nephew to separation logic [O'Hearn et al. 2001;Reynolds 2002], which have shown potential to bring more of the benefits of ownership type systems to high-level languages.
The key idea of reachability types is to track reachability and aliases as type qualifiers, which is best demonstrated by a code example with ML-style references (types shown as comments): Qualifiers are sets of identifiers attached to types.Variable x is bound to a freshly allocated reference; its type qualifier tracks only x itself.When y is bound to x, the type qualifier of y tracks both x and y, indicating that both can be reached from y.
Previous work [Bao et al. 2021] has shown how reachability types elegantly support functional abstraction beyond what is available in Rust.For example, Figure 1 shows a program with escaping functions that can track the sharing of locally-defined resources, which cannot be expressed under Rust's "shared XOR mutable" constraint.In Figure 1, we define a counter function that returns a pair of functions to increase or decrease a mutable variable.Both the "increase" and "decrease" functions capture the local heap-allocated reference cell c and escape from c's defining scope.Once escaped, the name c is not meaningful in the outer scope.Reachability types use the outer function's self-reference p to model this escaping behavior and preserve the tracking of shared resources.In contrast, Rust does not allow two functions to capture the same variable in a mutable way, unless using dynamic reference counting to bypass the static ownership discipline.
The idea of tracking reachability at the type level gives rise to powerful reasoning capabilitiesmost importantly, when considering the absence of reachability, namely separation.Two terms are separate when their type qualifiers are disjoint.The metatheory of reachability types guarantees not only preservation of types but also preservation of separation: if two expressions have disjoint qualifiers, they will evaluate to disconnected object graphs at runtime.Taking reachability and separation as the fundamental building blocks of a type system stands in contrast to traditional ownership type systems that put heap invariants about unique access paths first and selectively reintroduce sharing via borrowing.Crucially, reachability and separation appear as more fundamental properties in the sense that formal accounts of Rust's type system [Jung et al. 2018] are typically expressed using separation logic as the meta-language.
Limitations of  * .While the reachability type system  * presented by Bao et al. has shown key advances with regards to reasoning about lifetimes and sharing in the presence of functional abstraction, there are still significant gaps on the way to smoothly integrating reachability types Fig. 1.An example (from [Bao et al. 2021]) demonstrating first-class functions supported by reachability types.The counter function returns two closures over a shared mutable reference (which is a fresh value before binding it to c).The return value is a pair typed with a self-reference p to express the capture of c by both closures.The self-reference introduced by the -notation is similar to DOT, but reachability types desugar it into function types (cf.Section 2.4 for the encoding and Section 3.1 for the formal syntax).Rust's type system prevents returning closures over local mutable references due to the "shared XOR mutable" restriction, and has to resort to dynamic reference counting to implement similar functionality.
into real-world high-level languages such as Scala and OCaml, especially regarding type abstraction and polymorphic data types that are missing from  * .
The key obstacle on the way is  * 's treatment of untracked values and fresh values.In real-world programs, not all values need to have their reachability tracked (e.g., pure functions and nonresource values).However, keeping tracked and untracked values apart is difficult when crossing abstraction boundaries.While the  * -calculus does support untracked values, it conflates untracked values and fresh values, where fresh values are tracked values (e.g., allocations) but have not been bound to known variables.In  * , untracked values can be upcast to be fresh.Although being sound, it comes at a loss in precision, which means that code cannot be generic over the tracking status of arguments (see Section 2.1 for detailed examples).This conflation of untracked and fresh values in the  * -calculus unfortunately leads to the following important limitations in expressiveness: • The type system provides no abstraction over tracked and untracked types, e.g., a function cannot work over both tracked and untracked arguments while faithfully tracking their reachability.• Qualifier-dependent function applications can only have shallow dependencies, i.e., the argument name can only occur in the outermost qualifier of the return type.• Partly due to the first restriction,  * does not support parametric polymorphism for either types or qualifiers.• Due to the conflation of untracked and fresh values,  * cannot support nested mutable references in the base system without extending it first with a flow-sensitive effect system and move semantics.This paper overcomes the above limitations of reachability types and proposes new variants of reachability types that track fine-grained lifetime properties for higher-order, imperative, and polymorphic languages.By rethinking reachability tracking and proposing a novel notion of freshness, we show how systems like  * can smoothly support precise reachability polymorphism and type abstraction.
Preserving Chains of Reachability.In Bao et al.'s system, qualifiers are assigned to include all transitively reachable variables, i.e., the reachability sets are eagerly saturated.However, this is not always necessary and leads to precision loss when the reachability set of a variable is refined to a smaller set using substitution.If the reachability set containing the variable is transitively (c)  models freshness using the marker, which prevents further upcasting via subtyping (beyond y).
Fig. 2. Illustration and comparison of different reachability tracking mechanisms.We use solid lines for direct reachability, and dashed lines for reachability that is unobservable in the current context (cf.Section 2.2.2). 2a illustrates prior work by Bao et al. [2021], 2b reflects both this work and Scala capture types [Odersky et al. 2022], and 2c illustrates the unique feature of this work, which prevents upcasting through "fresh" variables, and thus allows substituting fresh variables with larger (but observably separate) reachability sets during evaluation.Thus, this work subsumes the essential aspects of both  * (separation) and capture types (qualifier refinement using subtyping).
saturated, the now superfluous elements cannot be removed, unless one would recompute the transitive closure from scratch.
In contrast, the new design proposed in this paper tracks one-step reachability by default, and only computes transitively saturated reachability sets on demand, e.g., before computing intersections to check separation (see Section 2.2.5).The two different mechanisms are illustrated in Figure 2. In Figure 2b, x only tracks its immediate reachable variables, namely {y, w}, whereas in Figure 2a,  * tracks all variables that can be transitively reached from x.
Importantly, tracking one-step reachability preserves chains of reachability, which allow us to maintain higher precision across substitution, both as part of dependent function application and during reduction steps.This approach yields a new notion of "maybe-tracked" values, whose tracking status solely depends on other variables from the context.For example, by refining reachability through the subtyping relation (see Section 2.2.4), x's reachability set in Figure 2b can be "upcast" to the empty set, which precisely reflects its true untracked status.
A New Freshness Notion.In Bao et al.'s system, untracked values are represented with the ⊥ qualifier and fresh values with the ∅ qualifier, indicating an empty set of reachable variables.Fresh values are tracked but not observably aliased in the context.Untracked values can be upcast to fresh values, but not vice versa.Treating a tracked value as untracked would be a soundness violation.
Instead of classifying values as untracked or tracked, we propose to classify them as potentially fresh or definitely non-fresh.To this end, we introduce an explicit freshness marker in qualifiers for fresh values.With that addition, untracked values are naturally assigned the empty reachability set, thus eliminating ⊥ in the new system.The freshness marker in qualifiers indicates that the expression may reach unobservable variables or locations, which will materialize during evaluation.Since signifies a statically unknown reachability set, it serves as a barrier in subtyping chains, so that one cannot upcast beyond .Figure 2c shows such an example where upcasting is blocked by the freshness marker on y.However, it is still possible to eliminate w in the qualifier {y,w} since its leaf nodes are in fact untracked entities.
Polymorphic Reachability Types.With the new mechanism for tracking reachability chains and the new freshness notion, we present the  -calculus based on the simply-typed -calculus.Compared to Bao et al.'s  * , the  -calculus addresses the fundamental expressiveness limitations in  * from above:  features precise reachability polymorphism without explicit quantification, it supports deep dependencies in qualifier-dependent applications, and supports nested references.Furthermore, on top of the  -calculus, we develop extensions with bounded quantification over types and qualifiers, leading to the F <: -calculus that can express polymorphic data types.Polymorphic data such as pairs in F <: track precise reachability of their components, which is not supported in  * .
Contributions.We summarize our contributions as follows: • We identify the root issues in prior work leading to imprecise reachability tracking and address them by preserving transitive chains of reachability based on a more explicit "freshness" representation.We explain the key ideas and demonstrate the new type system informally with examples (Section 2).• We present the formal theory and metatheory of (1) the  -calculus with precise reachability polymorphism that improves over Bao et al. [2021]'s  * -calculus (Section 3), and (2) the F <:calculus with bounded type-and-qualifier abstraction as an F <: -style extension of  (Section 4).We prove type soundness and preservation of separation property for both calculi.• We demonstrate that our system enables richer expressiveness in programming with capabilities compared to Scala capture types (Section 5).Our system thus subsumes both the original reachability types  * [Bao et al. 2021] and the essence of capture types [Odersky et al. 2022].• We have mechanized the metatheory of  and F <: in Coq, including all the results and examples in this paper.We have also implemented a prototype implementation that can typecheck the examples in the paper.The Coq mechanization and prototype can be found at https://github.com/TiarkRompf/reachability.
Section 6 discusses related work and Section 7 concludes the paper.

KEY IDEAS AND MOTIVATING EXAMPLES
We start by reviewing the reachability type system  * [Bao et al. 2021], its limitations with regards to reachability polymorphism, and then discuss our solution to this problem.Our work shares a large proportion of the surface-language syntax with Bao et al. [2021], but differs in typing and semantics.In examples, we use magenta for Bao et al.'s type system and blue for ours.Table 1 summarizes the key differences between the two systems and highlights the main improvements made in this paper.First-time readers may safely skip this table.
2.1 Revisiting  * and Its Limitations 2.1.1Reachability Sets as Qualifiers.The  * type system annotates types with reachability sets as qualifiers, tracking the variables in the current environment that may be reached by following memory references from the result of an expression.For example, consider an alloc() function that yields a new resource of fixed type T (e.g., a file handle).The qualifier of the result is the empty set alloc(): T ∅ , since as a fresh value, it cannot reach any variables in the current environment.When bound to a variable x, an invocation of alloc() is not considered fresh anymore as x reaches x itself: Similarly, assigning a variable to another variable propagates reachability by growing the set: Reachability is not symmetric, and stronger than aliasing, e.g., y reaches x, but x does not reach y.It is cheaper to compute than full aliasing and yet sufficient to check separation (Section 2.2.5).A function type has a self-reference (e.g., f above, often omitted), which can be used as an upper bound of its captured reachability set, i.e., it holds that {c,x,y} <: {f}, but only inside the function body.This is a mechanism for typing escaping closures (cf.Section 3.2.6),e.g., when a function capturing c escapes its defining scope, we can abstract over the now free variable as follows: Function return types also track reachability and may mention other variables in the context, indicating possible aliases.Argument qualifiers indicate the permissible overlap between a call-site argument and the function's reachable set.Consider the following identity function: Its argument qualifier is the empty set, demanding that the argument cannot be aliased with the free variables of the function.This is the observable separation guarantee of reachability types.
2.1.4Reachability Polymorphism and its Limitations. * provides a lightweight form of reachability polymorphism via dependent function applications, e.g., consider the id function from above: val x: T {x,a,b} = ...; id(x) // : T {x} [x↦ →{x,a,b}] = T {x,a,b} val y: T {y,z} = ...; id(y) // : T {x} [x↦ →{y,z}] = T {y,z} The type of id mentions no explicit quantifiers, and yet can be regarded as polymorphic over a fixed base type T with any reachability qualifier , as long as  is disjoint from id's reachability set.
Since id itself has an empty qualifier, any  is acceptable.
The Root of the Problem: Confusing Untracked with Fresh Values.The problem with reachability polymorphism in  * is its non-parametric treatment of untracked versus tracked arguments, e.g., the id function conflates these two different instantiations: Qualifier substitution with untracked status yields {x}[x ↦ → ⊥] = ∅ a tracked qualifier without known aliases (i.e., fresh).Bao et al. (Section 3.4) made this design choice to ensure soundness, but it introduces imprecision in tracking status and constitutes a severe limitation in expressiveness.
No code path can be generic with respect to the tracking status of arguments!To see why admitting a more precise qualifier T ⊥ for id(z) is unsound, we can postulate this "more precise" behavior (i.e., assuming {x}[x ↦ → ⊥] = ⊥) and subvert the type system.Consider the function fakeid returning a fresh tracked value each time: This function typechecks since the body expression has type T ∅ , which is a subtype of the declared return type T {x} .Under the postulate, applying fakeid with a non-tracking arguments results in But fakeid(y) actually returns a fresh value of qualifier ∅ that should never be down-cast to untracked!This violates the separation guarantee of the type system: a tracked value cannot escape as an untracked value.Otherwise, it can no longer be kept separate from other tracked values.
To summarize, reachability polymorphism via dependent application in  * must sacrifice parametricity and precision for soundness, leading to a confusion of untracked with fresh values.There is no easy fix with the binary track/untrack distinction, and we must rethink reachability polymorphism and the notion of freshness.

Precise Reachability Polymorphism in 𝜆
We propose the  -calculus, which features a new treatment of freshness and a finer-grained reachability assignment, leading to a well-behaved and more precise notion of reachability polymorphism that smoothly scales to type-and-qualifier abstraction.

One-
Step Reachability Tracking.Bao et al. [2021] use an "eager" strategy to track aliases: typing relations assign saturated qualifiers, i.e., these qualifiers are large enough to include all transitively reachable variables.In contrast,  keeps reachability sets minimal in type assignment and only computes transitive closures on demand (cf.Section 2.2.5), which ensures that we can preserve chains of reachability and refine elements in the chain later by substitution or subtyping.
The "eager" and "on-demand" tracking strategies each treat variable bindings differently (typing context shown to the right of ⊣): In the eager version (left), y reaches {x, y}, transitively including x's reachability set from the context.The on-demand version (right) only assigns the one-step reachability set {y}.It can be scaled to the saturated set by subtyping ({y} <: {x,y}) which includes the subset relation.On-demand tracking preserves the chains of reachability in typing: during reduction steps, qualifiers in the chain can be replaced with smaller reachable sets, leading to an increase in precision via substitution.

2.2.2
Freshness Marker .We model potential freshness by adding a marker to qualifiers, connecting static observability with evaluation.Consistent with observable separation (Section 2.1.3),a type  { } describes expressions which cannot reach the currently observable variables, but they may reach unobservable variables, including new references.The prime example is the reduction of allocations: , where ℓ is a fresh location value Before reduction, alloc() is fresh, i.e., it must be tracked but is not bound to a variable.Afterwards, we have a new and definitely known store location, which is considered not fresh, thus vanishes.
The presence of indicates that reduction steps may grow the qualifier, and its absence indicates that they will not.Bao et al.'s track/untrack system assumes that any tracked qualifier might grow.The marker also serves as a "contextual freshness" indicator for function parameters, e.g., here is the reachability-polymorphic identity function in  : The type specifies that id (1) cannot observe anything about its context (∅), and (2) it accepts arguments that may reach any unobservable variables.Thus, the id function accepts T arguments with any qualifier and the function body can only observe a fresh argument.Adjusting parameter qualifiers permits controlling the overlap between functions and their arguments, e.g., consider variants of id which close over some variable z in context: The qualifiers on the function type and the parameter specify the reachability information that the implementation can observe about its context (only z here), and about any given argument, respectively.It also says that the implementation is oblivious to anything it cannot observe.Function id2 accepts arguments reaching anything that does not (directly or transitively) reach z.But id3 permits z in the argument's qualifier, effectively allowing any argument.Finally, id4's parameter lacks the freshness marker, constraining arguments to be contextually non-fresh.That is, only observable arguments which reach at most z are allowed.
With the freshness marker, it is no longer necessary to use ⊥ to indicate untracked values.In  , qualifiers of untracked values (e.g., primitive values) are simply denoted by the empty set ∅.The key design difference here is having the marker in qualifiers to explicitly communicate (non-)freshness which is preserved by dependent application and substitution.Consider a function that mutates a captured reference cell and returns the argument.We annotate that the argument x is potentially aliased with the captured argument c1.However, in Bao et al. [2021]'s system, this potential alias is propagated to the return type qualifier and we cannot get rid of it even when applying with a non-overlapped argument c2:
Chasing the typing assumptions, both {x} <: ∅ and ∅ <: {x} hold in the above context, which justifies the equivalence.This reasoning step uses a subtyping rule for looking up qualifiers of bound variables in the context (see Section 3.2.6), which permits smaller, context-dependent steps to form reachability chains as long as qualifiers in the chain are all non-fresh.Therefore, id(y) cannot be upcast since its one-step reachable variable y is fresh: ← bound and tracked id(y) // : T {y} ← bound and cannot further upcast since y fresh 2.2.5 On-Demand Transitivity.When does the type system actually need to compute saturated qualifiers with the "on-demand" tracking strategy (Section 2.2.1)?Applying functions that expect fresh arguments is the only situation where this is necessary.For example, consider a function f that does not permit overlap between the argument's qualifier and its own reachable set: The application f(c2) should be rejected due to the lack of separation between c2 and f.Since the one-step reachability strategy lets variable bindings reach only themselves by default, naively intersecting the function and argument at the call site would not detect that c2 overlaps with f through c1.Thus, a sound overlap check at call sites must first compute saturated upper bounds on demand, and then compute their intersection.We discuss the formal details of saturated qualifiers and overlap checking further in Section 3.2.1.Finally, it is worth noting that c2's qualifier cannot be upcast through its reachability chain c1 to { } via subtyping, which would result in unsound overlap checking (cf.Section 3.2.6).
2.2.6 Qualifier-Dependent Application.Recall that the  * -calculus achieves reachability polymorphism via dependent function application (Section 2.1.4).That is, given a function type  ( : 2 , both  and  may occur in the codomain qualifier  2 , but the system forbids occurrences within  2 to ensure a sound treatment of escaping closures [Bao et al. 2021].Therefore, only shallow dependencies are allowed in applications. The root cause is that all tracked qualifiers in  * can potentially grow with unobservable reachability sets.Due to  's refined freshness-marker model, we can distinguish fresh/growing from non-fresh/static qualifiers, and safely permit occurrence of  and  deeply in  2 in the latter case (cf.Section 3.2.4)without precision loss.Consider the following function returning another function: We can assign the reachability set of f's innermost return type, depending on the outer argument x.
The dependent application f(c) yields a precise type, whereas the  * -calculus would have to upcast the returned function type to a self-reference before application (thus introducing imprecision).

Type-and-Qualifier Abstractions in F <:
Because of its confounding of fresh tracked values and untracked values, the  * -calculus lacks type abstraction mechanisms such as generic types.In contrast, we can smoothly extend  with type-and-qualifier abstractions in the style of  <: [Cardelli et al. 1994].
Type Abstractions.The first step towards F <: is to add F <: -style quantification over proper types without qualifiers.This is already attractive and enough to express the identity function with both type and lightweight reachability polymorphism.The following definition of id adds the type parameter T and does not require F <: -style abstraction of qualifiers: As in F <: , we add an upper bound Top of all types to the system.However, reachability sets attached to proper types must be concrete and cannot be abstracted over.
Qualifier Abstractions.We now introduce an abstract qualifier and an upper bound qualifier in the style of F <: .In this way, the polymorphic identity function is a shorthand notation that does not need to use the abstract qualifier.The fully desugared term is where z is the abstract qualifier variable bounded by .One could further omit the abstract qualifier, type-and-qualifier bound, and return type using the shorthand notation shown above.
Although the additionally introduced abstract qualifier (z) does not yield further expressiveness for the identity function, quantified qualifiers vary independently of the type variable, and one is free to attach them to any proper type.In Section 4, we present the formalization of F <: , which combines  with F <: -style polymorphism for bounded type-and-qualifier abstraction.

Polymorphic Data Types
In this section, we consider typing polymorphic data types under F <: and demonstrate the expressiveness gain from type-and-qualifier polymorphism.Suppose we have extended the language with native pair types, how should their typing rules look like?There are two main design goals: • First, we would like to precisely track the reachability of components, so a pair type Pair[A a , B b ] annotates qualifiers to components.Moreover, the projection functions should preserve precise reachability whenever possible.For example, given an expression of type Pair[A a , B b ], retrieving its components should yield exactly the same qualifiers we put in: The above snippet creates a pair of two reference cells and then gets its components.Explicit type applications are omitted and can be inferred as in Scala (e.g., by bidirectional typing [Pierce and Turner 2000]).
• Second, we would like to allow pairs capturing local variables to escape from their defining scope (e.g., the counter example in Figure 1).To this end, we designate a self-reference p for pairs p.Once the pair is bound to a variable, we "unpack" the self-reference so that projections are properly aliased.Aiming for minimality, the rest of this section investigates the typing of Church-encoded pairs that satisfies our desired typing and subtyping rules.We discuss two different types of encodings: "transparent" and "opaque" pairs corresponding to the two usage scenarios above.Transparent pairs track precise reachability of components using F <: 's parametric qualifiers and can only be used under appropriate contexts.Opaque pairs use self-references as an abstraction to hide local qualifiers and can escape to an outer scope.Finally, the subtyping rule connecting both is justified by a coercion function that eta-expands pairs, converting transparent pairs to opaque pairs.When using the quantified type for the argument or return type, its accompanying qualifier is implicitly attached, i.e., we write A as a shorthand of A a when using it.
The projectors fst and snd have the usual definition but using accurate types and qualifiers: By making the elimination type C's qualifier parametric, we can now instantiate it in the projection function with the precise component qualifiers, as shown by the example at the beginning of this section.The general Church-encoding of data types via sums and products can also benefit from the increased precision.
2.4.2Typing Escaped Church Pairs, Opaquely.The transparent pair typing works for cases where the components are still in the context, but the pair cannot escape from that scope (cf. Figure 1).We now discuss the types of escaped pairs using self-references as abstraction.To avoid confusion, we name the type and constructor of opaque pairs as OPair, and transparent pairs remain Pair.
Note that in F <: universal types and type abstractions also have self-references (e.g., p in the definition) that can be used to express escaping polymorphic closures, similar to their term-level correspondences (e.g., h in the definition).Therefore, the self-reference in p.OPair is just an syntactic annotation referring to the self-reference of the universal type.Compare to the transparent typing, here we do not use quantified qualifiers that are parametrically introduced.Instead, we use a chain of self-references in the codomains, upcasting from the inner most reachability {x, y} to h and to p.
The introduction and elimination forms of opaque pairs also reflect the typing using self-references: def Imprecise Eliminations.While the typing works out, the resulting qualifiers of the projections fst/snd are imprecise.We have no means to vary the qualifier of the elimination type C in type OPair.
When the component qualifiers are not available in the context, using the self-reference to track possible sharing is the most accurate option.This is the intended design as shown in the beginning of this section.A side effect of such typing is that in-scope elimination can yield the set of joint qualifiers, since the pair can reach them by our "maybe-tracked" notation: ... // u and v defined as before From a pragmatic perspective, when the language is extended with pairs as native algebraic data types, the eta-conversion justifies an admissible subtyping rule for escaped pairs.It is important to note that both the transparent and opaque pair encodings use the same terms, namely the standard System-F encoding, just with different assigned type qualifiers.We expect that the core F <: typing and subtyping rules can be refined or extended to enable a uniform encoding so that the eta-conversion step becomes unnecessary.

Nested Mutable References
The base type system of Bao et al. supports reference cells that can only store "untracked" values or pure computation (i.e., of qualifier ⊥).This compromise is again due to conflating untracked and fresh values in  * .To support more expressive nested references, the  * -calculus has to use a flowsensitive effect system with explicit move semantics.This is not at all required in the  -calculus, because its freshness model already supports a form of nested references.
The key idea is that a reference's content also carries a reachability annotation, e.g., Ref[T p ] q , where ∉ p.That is, only references with fully observable reachability are permitted, and these references remain invariant once introduced, and can only be assigned with values having the same reachability set.
This restrictive model for  can already express more interesting programs than  * .For example, we could define complex heap-allocated data structures or store effectful functions into reference cells.Recall the counter example (Figure 1) that returns two functions to increase or decrease an encapsulated state 1 .Both functions share the same reachable set containing ctr: Note that incr and decr encapsulate and mutate a locally-defined heap reference cell, thus are effectful.We could create a reference cell that stores either the incr or decr function: This pattern permits more flexible uses of these capabilities, e.g., registering functions as callbacks or tracking permissible escaping via assignments.Section 3.2.5 discusses the formal rules of this restricted form of nested references.

SIMPLY-TYPED REACHABILITY POLYMORPHISM
This section presents the formal metatheory of the base  -calculus (Section 2.2), a generalization of the  * -calculus by Bao et al. [2021] that adds the notion of freshness markers for a more precise notion of lightweight qualifier polymorphism.

Syntax
Figure 3 shows the syntax of  which is based on the simply-typed -calculus with mutable references and subtyping.We denote general term variables by the meta variables , , , and reserve  , , ℎ specifically for function self-references in contexts where the distinction matters.
1 Because of the subtyping discussed in Section 2.4, we do not distinguish transparent and opaque pairs and assume the cast is implicitly applied when necessary.

Term Typing
Γ  ⊢  :   : Terms consist of constants of base types, variables, recursive functions  ().(binding the self-reference  and the argument  in the body ), function applications, reference allocations, dereferences, and assignments.
Reachability qualifiers , ,  are finite sets of variables that may additionally include the distinct freshness marker .Once we add store typings (Section 3.3), qualifiers will include store locations in addition to variables.For readability, we often drop the set notation for qualifiers and write them down as comma-separated lists of atoms.
We distinguish ordinary types  from qualified types  =   , where the latter annotates a qualifier  to an ordinary type  .The types consist of base types  (e.g., Int, Unit), references, and dependent function types  ( : ) → , where both argument and return type are qualified.The codomain  may depend on both the self-reference  and argument  in its qualifier and type.We Mutable reference types Ref  track the known aliases of the value pointed to by the reference.We also permit forms of nested references, which are prohibited in the base  * -calculus unless a flow-sensitive effect system is added [Bao et al. 2021].
An observation  is a finite set of variables which is part of the term typing judgment (Section 3.2).It specifies which variables in the static environment Γ are observable.The latter assigns qualified typing assumptions to variables.

Static Semantics
The term typing judgment Γ  ⊢  :  in Figure 4 states that term  has qualified type  and may only access the typing assumptions of Γ observable by .For  =   , one may think of  as a computation that yields a result value of type aliasing no more than , if it terminates.Alternatively, we could formulate the typing judgment without internalizing , and instead have an explicit context filter operation Γ  := { :   ∈ Γ | ,  ⊆  } for restricting the context in subterms, just like Bao et al. [2021] who loosely take inspiration from substructural type systems.Internalizing  (1) makes observability an explicit notion, which facilitates reasoning about separation and overlap, and (2) greatly simplifies the Coq mechanization.Context filtering is only needed for term typing, but not for subtyping, so as to keep the formalization simple.

Term Typing
Reduction Contexts, Values, Terms, Stores

One-
Step Reachability.Term typing usually assigns minimal qualifiers in the currently observable context.For instance, term variables  track exactly themselves (t-var), and can be used only if they are observable ( ∈ ).Similarly, constants of base types are untracked (t-cst).
We can further scale up the qualifier to include transitively reachable variables by subsumption (t-sub) if needed.This "one-step" treatment of reachability is sufficient for soundness, and shows that most of the time, we do not have to track fully transitive reachability, but instead may compute it on-demand where it matters, i.e., when checking separation and overlap in function applications (discussed further below).In contrast, Bao et al. [2021] implicitly ensures fully transitive reachability, i.e., term typing always assigns transitively closed qualifiers. 2 Their (t-var) rule would assign  , where  is transitively closed.One-step reachability simplifies the system and adds finer-grained precision over transitive reachability, since we can refine each step in a reachability chain as more information is discovered during evaluation.Dependent function application and abstraction with function self-references are prime examples (Section 3.2.4).else in the environment.We model this by setting the observation to , ,  when typing the body.Thus, its observation  at least includes the free variables of the function.To ensure well-scopedness,  must be a subset of the observation .In essence, a function type implicitly quantifies over anything that is not observed by , achieving a lightweight form of qualifier polymorphism.

3.2.3
Qualifier Substitution and Growth.The base substitution operation  [/] of qualifiers for variables is defined in Figure 6, and we use it along with its homomorphic extension to types in dependent function application.Rule (t-app ) applies to cases where the argument's qualifier is bigger than what the function type assumes, or is expected to grow bigger due to the freshness marker .These cases require more nuanced treatment and restrictions on the degree of dependency in the codomain.That is, if the argument or function is fresh, then the codomain's type  may not be dependent on the respective variable.Otherwise, type preservation is lost due to the potential growth with fresh runtime locations.In total, there are four possible cases, and we discuss two of them as specialized rules below (other cases are analogous).If neither the argument nor the function is fresh, we obtain Γ ⊢  1 :  ( : which permits unconstrained dependency in the codomain.If both the argument and function are fresh, we obtain Γ ⊢  1 :  ( : which requires that neither  nor  occur freely the codomain type  (as in Bao et al.).In all instances of (t-app ), since  is potentially bigger than the function codomain, we need to check for observable separation/overlap between function and argument, i.e., the portion of  that the function can observe should conform with the function parameter.This is the only place in the type system requiring fully reflexive-transitive reachability using the overlap operator  ∩  (Figure 6), which is the intersection of the smallest saturated reachability sets of  and , always including to indicate that the argument is allowed to have a bigger qualifier than the domain.For the type safety proof, it is also sufficient to just demand any saturated supersets.
Both function application rules impose an observability restriction on the codomain qualifier  ⊆ , ,  , which is to ensure that the resulting qualifier of term typings is always observable under  (Lemma 3.1), a critical property for the substitution lemmas and type soundness proof.

Mutable
References.The  * system by Bao et al. [2021] cannot express nested references without the addition of a flow-sensitive effect system.Although extending it with an effect system is possible, our type system readily supports a limited form of nested references by means of reachability and the fresh/non-fresh distinction.Qualifiers in reference types need to be non-fresh in (t-ref), (t-deref), and (t-assgn).On the outside, reference allocations (t-ref) track the referent's non-fresh qualifier and , because the final result will be a fresh new store location, which will be added to the qualifier.A limitation of this model is the invariance of the referent's qualifier, so that only values with identical qualifier can ever be assigned in (t-assgn).Therefore the referent's qualifier must be chosen large enough when introduced.Invariance is also reflected in the subtyping rule for references, discussed next.
3.2.6Subtyping.We distinguish subtyping between qualifiers , ordinary types  , and qualified types , where the latter two are mutually dependent.Subtyping is assumed to be well-scoped under the typing context Γ, i.e., types and qualifiers mention only variables bound in Γ, and so do its typing assumptions.Qualified subtyping (sq-sub) just forwards to the other two judgments for scaling the type and qualifier, respectively.
Qualifier Subtyping.Qualifier subtyping includes the subset relation (q-sub), the two contextual rules (q-self) and (q-var), and transitivity (q-trans).Rule (q-self) is inherited from Bao et al. [2021], and used for abstracting the qualifiers of escaping closures (see examples in Section 2.1.3and Section 2.3), i.e., if a function self reference  and its assumed qualifier  occur in some qualifier context, then we may delete  and just retain  , because  may contain captured variables that are not visible in an outer scope.Rule (q-var) is new here and critical for one-step reachability: a qualifier ,  is more precise than ,  since substitution may replace  with a smaller qualifier than  later (cf.Section 2.2.3).This is only valid if ∉ , because otherwise,  could be replaced later with a larger set than  and we would lose track of it.The same restriction applies to (q-self).
Ordinary Subtyping.Subtyping rules for base types (s-base), reference types (s-ref), and function types (s-fun) are standard modulo qualifiers.Reflexivity and transitivity are both admissible for subtyping on ordinary and qualified types.References are invariant in the enclosed qualifier and equivalent in the value, expressed by bidirectional subtype constraints.Function types are contravariant in the domain, and covariant in the codomain, as usual.Due to dependency in the codomain, we are careful to extend the context with the smaller argument type and self reference.Importantly, the function self-reference added to the context only carries the marker.This distinguishes computationally relevant self-references introduced by term typing in (t-abs) from synthetic ones for subtyping.Only the former is eligible for abstraction by function self-references.

Dynamic Semantics and Stores
The  -calculus adopts the standard call-by-value reduction of the -calculus with mutable references and a store (Figure 7).Term typing and subtyping change accordingly to include store typings Σ, and both qualifiers and observations may now include store locations from dom(Σ).Typing a location value (t-loc) requires that it be observable, along with the full qualifier of the referent (, ℓ ⊆ ).This model implements the fully transitive reachability notion for store locations instead of one-step reachability (in contrast to variables, Section 3.2.1),as we never substitute store locations and thus do not alter the assumed qualifiers in the store typing Σ.The well-formedness predicate Σ ok ensures that all assumptions in Σ are closed and have transitively closed qualifiers consisting only of other store locations.Well-formedness is required by Corollary 3.8 to ensure fully disjoint reachability chains and object graphs.

Metatheory
The  -calculus exhibits syntactic type soundness which we prove by standard progress and preservation properties (Theorems 3.6 and 3.7).Type soundness implies the preservation of separation corollary (Corollary 3.8) as set forth by Bao et al. [2021] for their  * -calculus.It is a memory property certifying that the results of well-typed  terms with disjoint qualifiers indeed never alias.Below, we discuss key lemmas required for the type soundness proof, which has been proved in Coq.Due to space limitations, we elide standard properties such as weakening and narrowing.
3.4.1 Observability Properties.Reasoning about substitutions and their interaction with overlap/separation in preservation lemmas requires that the qualifiers assigned by term typing are observable.The following lemmas are proved by induction over the respective typing derivations: Lemma 3.1 (Observability Invariant).Term typing always assigns observable qualifiers, i.e., if Well-typed values cannot observe anything about the context beyond their assigned qualifier: It is easy to see that any observation for a function  (). will at least track the free variables of the body .Finally, well-typed values are always non-fresh in the following sense: This lemma is important for substitution, and asserts that values only reach statically fully known variables and locations in context.That is, we may safely assume that values are never the source of , and it can only stem from subsumption, which we may undo by Lemma 3.3.Ruling out for values ensures that we do not accidentally add it when it is expected to be absent in a substitution target .The absence indicates that a substitution on  will not increase it with fresh locations.
3.4.2Substitution Lemma.We consider type soundness for closed terms and apply "top-level" substitutions, i.e., substituting closed values with qualifiers that do not contain term variables, but only store locations.The proof of the substitution lemma critically relies on the distributivity of substitution and the overlap operator (Figure 6), which is required to proceed in the (t-app ) case: Lemma 3.4 (Top-Level Substitutions Distribute with Overlap).
Qualifier substitution does not generally distribute with set intersection, due to the problematic case when the substituted variable  occurs in only one of the saturated sets  * and  ′ * .Distributivity holds if (1) we ensure that what is observed about the qualifier  we substitute for  is bounded by what the context observes about , i.e.,  ∩  ⊆  for  :   ∈ Γ, and (2) ,  are top-level as above.
In the type preservation proof, -reduction substitutes both the function parameter and selfreference in (t-abs) (Figure 4) for some values.The two substitutions can be expressed by sequentially applying a general substitution lemma on one variable: Lemma 3.5 (Top-Level Term Substitution).

Static Semantics
The typing and subtyping rules of F <: (Figure 8) are a superset of those presented for  in Section 3. 4.2.1 Typing Rules.We add the typing rules for type abstractions and type applications.The type system is defined declaratively in Curry-style, and hence for type abstractions (t-tabs) we need to "guess" the whole universal type and its qualifier.Other parts are analogous to term abstraction typing (Section 3.2.2).Notably, observable separation naturally generalizes to type abstraction.That is, the qualifier  constrains what the type abstraction's implementation can observe, and 's qualifier in   <:  determines observable overlap/separation for instantiations of .Especially, if  mentions the freshness marker , then instantiations of  can mention unobserved variables.
Similar to function applications in  , there are two type application rules: (t-tapp) for nonfresh dependent applications and (t-tapp ) for restricted dependent applications.Requiring nonfreshness ensures that we pass a qualifier argument that is bounded by other variables in the context.Rule (t-tapp ) is analogous to (t-app ) (cf.Section 3.2.4):If the argument/function qualifier is fresh, then the result type  cannot be dependent on it.We impose observability constraints on the codomain qualifier  to ensure the observability invariant (Lemma 3.1) for F <: .4.2.2Subtyping Rules.Rule (s-top) is the standard rule for the Top type.Instead of defining a single subtyping rule for type-and-qualifier variables that simply looks up the context in the premise, we disentangle it to the (q-qvar) rule and (s-tvar) rule, distinguishing the subtyping for qualifiers and ordinary types (cf.Section 3.2.6).The former accounts for subtyping of qualifiers, allowing upcasting a qualifier variable to its upper bound.The latter is akin to standard type variable subtyping.This disentanglement reflects the fact that we can upcast the quantified qualifier and type independently, despite that they are introduced together using a combined syntax.
For universal types (s-all), we use the "full" subtyping rule for richer expressiveness [Curien and Ghelli 1992] where type bounds are contravariant.This rule renders subtyping an undecidable relation [Pierce 1992].However, the choice of using the "full" variant is not essential to our calculus and our main metatheoretic result is type soundness and preservation of separation.It should be possible to obtain a decidable fragment by building atop the "kernel" variant of F <: .In this case, we would need to check subtyping of the type argument explicitly, rather than relying on subsumption to upcast the type of the type abstraction itself.Due to self-references, we also extend the context with the smaller universal type when subtyping the body, as in DOT [Rompf and Amin 2016].Note that (s-all) invokes subtyping on qualified types in its premises.

Dynamic Semantics and Metatheory
Figure 7 highlights the changes and new rules of F <: 's dynamic semantics, as an extension of  .The reduction semantics is entirely standard compared to F <: , the only difference being that type abstractions are recursive, so that type application (  ) also substitutes the type abstraction itself along with the argument.Since location typing (t-loc) and store well-formedness require closed types in store typings, we additionally demand the absence of free type variables.
F <: enjoys the same soundness properties as  , i.e., progress, preservation, and the separation of preservation corollary (cf.Section 3.4.3).As for  , we have proved these results in Coq for F <: .

COMPARISON WITH SCALA CAPTURE TYPES
A closely related work to ours is the recent Scala Capture Types (CT) proposal [Boruch-Gruszecki et al. 2021;Odersky et al. 2021Odersky et al. , 2022] ] which also tracks sets of variables.The system is tailored to programming with effects as non-escaping capabilities, providing a lightweight form of effect polymorphism.In this section, we inspect a few aspects of CT and compare with reachability types.
5.1 Capture Sets and The Universal Qualifier Similar to our system, capture types are built on top of F <: and types can be annotated with variable sets, i.e., { 1 , . . .,   }  where   is a variable representing the captured capability.Here is a (simplified) combinator for scoped exception handling using capture types [Odersky et al. 2021 Importantly, { * } is a special marker for the top element for qualifier subtyping in capture types, meaning some unknown set of variables is tracked, e.g., { * } CanThrow above.
While superficially similar, this top qualifier should not to be confused with our marker indicating a fresh/growing qualifier, and behaves differently, as we will show later.

Box Types for Non-Escaping Capabilities
Since c represents a universal capability, we want to enforce that the lifetime of capability c passed to the given block is bound to the scope of _ try.In other words, it should not be leaked for any given block, e.g., by directly returning it or returning it indirectly through an escaping closure.Capture types enforce this by requiring that the universal capability { * } cannot escape.This is in contrast to capabilities bound to a variable in an outer scope.
When combined with parametric type polymorphism, Odersky et al. [2021Odersky et al. [ , 2022] ] propose to use a box type operator □  to turn qualified types into proper, unqualified types, so that type variables only need to range over proper types.A boxed value □[  ] capturing local variables in  is upcast to □[{ * }  ] when going out of scope.Unboxing such types recovers the capture set.Boxed values can only be unboxed if the contained qualifier  is a concrete variable set, specifically excluding the top qualifier { * }.This provides a mechanism for statically enforcing non-escaping capabilities, i.e., boxes are implicitly inserted at the abstraction boundary whenever the block's return type A is instantiated with a tracked type: On the outside, subtyping can only assign the { * } qualifier to blocks that return or capture the capability c, since the captured variable is not visible in the outer scope.

Limitation: Tracking Fresh Values
Let us now consider combining _ try with other resources that have non-scoped introduction forms and should be tracked: The compiler rejects unboxing the { * } qualifier, but allows it for any more concrete one.However, while the box type prevents capabilities from escaping, the compiler must infer and insert box introductions and eliminations at declaration and use sites of polymorphic terms.But more importantly, the capture types mechanism does not support unbound fresh values well, e.g., fresh allocations.The obvious choice is assigning the top-qualifier { * } to indicate some new value, but this is at odds with boxing/unboxing, e.g., one cannot write This solution works well for effects-as-capabilities models, but it is unsatisfactory for tracking aliasing and separation, e.g., all fresh values have a common super type {heap} T which pollutes subtyping chains and leads to a loss of distinction between separate fresh allocations.In summary, if we want to track the lifetimes of a given class of resources, these lifetimes must be properly nested in a stack-like manner with the lifetimes of all other resources.

The Reachability Approach
Our F <: -calculus can correctly handle fresh values, while at the same time not requiring a box type.This stems from (1) having an intersection operator for reasoning about separation/overlap, and (2) a strong observability guarantee on function types and universal types.For instance, here is the type-and qualifier-polymorphic version of _ try: The annotation on the block parameter specifies that it is contextually fresh for the implementation of _ try and thus entirely separate in terms of transitive reachability.We still reject the x and y examples above (Section 5.2), but we now permit fresh: T and fresh2: f(() => T {f} ), which correctly preserves the freshness of unnamed results.Finally, F <: permits finer-grained type distinctions when returning fresh values, due to function self references: F <: distinguishes between returning a fresh value on each invocation, versus returning one and the same fresh value escaping a local scope, whereas both are indistinguishable in capture types.
Outlook.Compared to CT, reachability types exhibit similar expressiveness and can support all uses of capture types.Additionally, reachability types show richer expressiveness in a few key aspects, especially the tracking of freshness and the guarantee of separation.In future work, we propose to implement reachability types on top of the experimental implementation [Odersky et al. 2023] of capture types in Scala 3, which would additionally provide a notion of separation.We look forward to seeing how these two lines of similar ideas can benefit each other in the future.
On the other hand, it is possible to further increase the expressiveness power of reachability types by layering a flow-sensitive effect system on top of it.With the notion of reachability, we can soundly express uniqueness, use-once capabilities, and move semantics as described in [Bao et al. 2021].These are useful to model low-level memory deallocation, one-shot continuations, lock/unlock in concurrency programming, etc.

RELATED WORK
Tracking Variables in Types.The most directly related work of this paper is the original work on reachability types [Bao et al. 2021].This paper addresses the limitation of Bao et al. [2021] and improves its expressiveness by introducing a new reachability tracking mechanism, the freshness notion, and type-and-qualifier quantification.
Capture types [Boruch-Gruszecki et al. 2021;Odersky et al. 2021Odersky et al. , 2022] ] is another recent ongoing effort to integrate capability tracking and escaping checking into Scala 3. Several calculi have been proposed for capture types, e.g., CF <: [Boruch-Gruszecki et al. 2021] and CC <:□ [Odersky et al. 2021[Odersky et al. , 2022]].In Section 5, we have discussed and compared with capture types.To achieve capture tunnelling with universal polymorphism, the CC <:□ calculus uses boxing/unboxing, inspired by contextual modal type theory (CMTT) [Nanevski et al. 2008].Scherer and Hoffmann [2013] propose open closure types where function types are attached with its defining lexical environment.It is used for data flow analysis.Several type systems [Jang et al. 2022;Kiselyov et al. 2016;Parreaux et al. 2018] designed for manipulating open code in metaprogramming also track free variables and contexts in types, which are closely related to CMTT.
Escaping, Freshness, and Existential Types.Works inspired by regions [Tofte and Talpin 1997] use existential types for tracking freshness or escaping entities, e.g., in Alias types [Smith et al. 2000],  3 [Ahmed et al. 2007], and Cyclone [Grossman et al. 2002], analogous to our freshness marker and self-reference.As an analogy, one can think of a type with the freshness marker Ref as having an underlying quasi-existential type  .Ref {x} where the reference type tracks its own self-reference.However, existentials for this purpose in our system would have to preserve precise reachability information across temporary aliases created during pack/unpack operations.That is, special facilities simulating freshness marker and related constructs would need to be used in the implementation of existentials, if those were taken as primitives.Therefore, we believe the typing with self-references is more concise and appropriate than existentials here, because we can use the same variable.In addition, the use of self-references for escaping closures in our work makes the reasoning succinct.Similar to our calculi, type systems distinguishing second-class values can also enforce non-escaping properties of effects or capabilities [Brachthäuser et al. 2022[Brachthäuser et al. , 2020;;Osvald et al. 2016;Siek et al. 2012;Xhebraj et al. 2022].To regain the ability to return second-class capabilities, Brachthäuser et al. [2022] again make use of boxing and unboxing.
Separation.The notion of separation and intersection operator (Section 3.2.4)used in reachability types is inspired by separation logic [O'Hearn et al. 2001;Reynolds 2002] and its predecessors [O'Hearn et al. 1999;Reynolds 1978Reynolds , 1989].Bunched typing [O'Hearn 2003] and syntactic control of interference [O'Hearn et al. 1999;Reynolds 1978Reynolds , 1989] allow reasoning about disjoint and shared resource access.This is similar to reachability types, however, our system does not enforce that the computations of the function and arguments are disjoint, but their final values are disjoint (rule t-app in Figure 4).Bunched typing enforces separation by splitting the typing context, whereas our work enforces separation by checking disjointness of saturated reachability sets.Bunched typing also lacks an explicit treatment of aliasing.
Uniqueness types [Barendsen and Smetsers 1996;de Vries et al. 2006de Vries et al. , 2007] ] ensure that there is no more than one reference pointing to the resource, effectively establishing separation.Marshall et al. [2022] present a language unifying linearity [Wadler 1990] and uniqueness.Our base system does not directly track either linearity or uniqueness, instead, flow-sensitive "kill" effects that disable all aliases can be integrated to statically enforce uniqueness [Bao et al. 2021].
Polymorphism.Reachability types and our variants feature lightweight reachability polymorphism without introducing explicit quantification (cf.Section 3.2.2).Capture types [Boruch-Gruszecki et al. 2021;Odersky et al. 2021Odersky et al. , 2022] ] provide a similar flavor via dependent function application.Brachthäuser et al. [2022Brachthäuser et al. [ , 2020] ] propose to represent effects as capabilities, which yields a lightweight form of effect polymorphism that requires little annotations.
Various forms of polymorphism exist in prior work on ownership types.Noble et al. [1998] uses generic parameters to pass aliasing modes into a class.But they do not allow ownership parameterization isolated from type parameterization.Clarke [2003] further supports ownership polymorphism via context parameters.Similarly, Ownership Generic Java [Potanin et al. 2006] allows programmers to specify ownership information through type parameters.Jo∃ [Cameron and Drossopoulou 2009;Cameron 2009] combines the theory of existential types with a parametric ownership type system, where ownership information is passed as additional type arguments.Generic Universe Types [Dietl et al. 2011] integrate the owners-as-modifiers discipline with type genericity, effectively separating the ownership topology from the encapsulation constraints.Collinson et al. [2008] combine F-style polymorphism with bunched logic, where universal types are discerned to be either additive and multiplicative, but do not allow abstraction over additivity and multiplicativity.Our system F <: has quantified abstraction over qualifiers, which can be used as an argument's reachability, permitting flexible instantiation of either disjointness of sharing.
Constraints in alias types [Smith et al. 2000] support a form of location and store polymorphism, where the latter abstracts over irrelevant store locations.Our calculi implicitly abstract over contexts by baking the observability notion into typing.
Ownership Types.Ownership type systems [Clarke et al. 1998;Noble et al. 1998] are generally concerned with objects in OO programs and start from the uniqueness restriction [Boyapati et al. 2002;Clarke et al. 2001;Dietl et al. 2011;Müller and Poetzsch-Heffter 2000;Zhao et al. 2008] and then selectively re-introduce sharing in a controlled manner [Clebsch et al. 2015;Hogg 1991;Naden et al. 2012].Inherited from Bao et al. [2021], our calculi are designed for higher-order languages and deem sharing and separation as essential substrates, on top of which an additional effect system can be layered to achieve uniqueness and ownership transfer.The focus of this paper is to address the limitations in expressiveness of Bao et al. [2021] regarding reachability and type polymorphism.
Rust's type system [Matsakis and Klock 2014] enforces strict uniqueness of mutable references, while immutable references can be shared via borrowing, known as the "shared XOR mutable" rule.Mezzo [Balabonski et al. 2016] is a language designed for control aliasing and mutation and share some similarities with F <: .Mezzo tracks aliasing using singleton types [Smith et al. 2000].When dealing with effects, Mezzo imposes restrictions like Rust: mutable portions of the heap must have a unique owner, whereas reachability types relax this constraint.Moreover, Mezzo lacks the notion of separation between functions and arguments and uses existential quantification to handle escaping functions that capture local variables.F <: checks separation at the call site and has a lightweight mechanism to track escaping functions via self-references.
Typestate-oriented programming [Aldrich et al. 2009] and its combination with gradual typing [Garcia et al. 2014] also provides static flow-sensitive reasoning or dynamic enforcement.

CONCLUSION
In this work, we investigate limitations in expressiveness found in prior reachability type systems [Bao et al. 2021].We propose a new reachability type system  that has lightweight, precise, and sound reachability polymorphism.Based on  , we add bounded quantification over types and qualifiers, leading to a type-and-reachability-polymorphic calculus F <: .We have formalized these systems and proved the soundness and separation guarantees in Coq.We further discuss applying F <: to programming with capabilities and compare with Scala capture types.Our system subsumes both prior reachability types and the essence of Scala capture types, while exhibiting richer expressiveness in key aspects such as modeling freshness and guaranteeing separation.

Table 1 .
Overview and comparison of  * and this work."-" indicates there is no equivalent notion in the system.The id function is the polymorphic identity function as defined in the respective system.
Substitution replaces the variable with the given qualifier, if present in the target.We suggestively overload the substitution notation for qualifier growth  [/ ].Capturing the intuition behind the freshness marker , growth adds  to  only if is present, and otherwise ignores .Growth abstracts over reduction steps that may allocate new reachable store locations in type preservation (Theorem 3.7).We do not remove to permit continuous growth.3.2.4 Dependent Application, Separation and Overlap.Function applications are typeable by rules (t-app) and (t-app ).The former rule applies if the function's parameter is non-fresh ( ∉ ) and it matches the argument, i.e., the argument qualifier reaches only bound variables and will not increase at run time.Applications in (t-app) are dependent, substituting the function and argument variable in the type and qualifier of the codomain with the given qualifiers (see Section 2.2.6).