Reference Capabilities for Flexible Memory Management

Verona is a concurrent object-oriented programming language that organises all the objects in a program into a forest of isolated regions. Memory is managed locally for each region, so programmers can control a program's memory use by adjusting objects' partition into regions, and by setting each region's memory management strategy. A thread can only mutate (allocate, deallocate) objects within one active region---its "window of mutability". Memory management costs are localised to the active region, ensuring overheads can be predicted and controlled. Moving the mutability window between regions is explicit, so code can be executed wherever it is required, yet programs remain in control of memory use. An ownership type system based on reference capabilities enforces region isolation, controlling aliasing within and between regions, yet supporting objects moving between regions and threads. Data accesses never need expensive atomic operations, and are always thread-safe.

runtime system, and manually return that memory when it was no longer required.LISP introduced the rst automatic memory management system-a garbage collector-which relieved programmers from the need to explicitly free memory, rather memory will be automatically reclaimed once it is no longer needed [Jones et al. 2016].As well as reducing the amount of bookkeeping code programmers have to write, garbage collection typically provides "memory safety" which prevents a number of characteristic errors common to manual memory management, such as failing to free objects that are no longer needed, or accidentally freeing objects that are still in use.
While there is now a 60+ year history of research in garbage collection algorithms and implementations, many programmers seem resistant to using garbage collection, despite the pitfalls of manual memory management.According to the TIOBE index of programming languages [Tiobe 2022], about half out of the top twenty programming languages eschew garbage collection, and rely on various forms of manual memory management.The 1st and 3rd of the top 25 Common Weaknesses in CWE 2020-2022 are writing and reading outside of allocated memory and using memory after freeing it is 7th.Memory leaks come in at 32nd and race conditions 33rd [CWE 2022].
In short, manual memory management (e.g., C/C++) is unsafe and prone to errors but allows programmers to leverage domain knowledge to optimise memory management.Some compile-time memory management (e.g., Rust) and automatic GC (e.g., Java/C#) avoids the memory unsafety, but instead leads programmers to write unsafe code for a variety of reasons.In Rust, programmers must use unsafe code to construct well-known data structures, and object topologies without clear domination.In Java, programmers use unsafe code [Mastrangelo et al. 2015] to leverage domain knowledge to optimise memory management and to make GC performance more predictable.In general, reasoning about the performance of automatic GC is made di cult by the systemic e ects of GC on program performance and the heuristics which control when and how GC is run.
Contributions.This paper introduces Verona's region system-Reggio-and its accompanying type system.Reggio gives programmers control over memory management costs by dividing a program's heap into a exible forest of independent regions.Programmers can pick a suitable strategy for how memory in each region is managed, irrespective of what other regions do.Within each thread, the programmer explicitly moves a single "window of mutability" through the region forest.The single window of mutability makes clear which region each part of a program is working within, and how the program a ects that region, in particular with respect to object liveness, and also permits a exible aliasing.As a region can only be made accessible through a single pointer, programs become free of data-races by design, and cheap ownership transfer to support recon guring the region topology is easy.Memory management overheads (e.g., tracing, and reference count manipulations) are likewise localised to just the mutable region.

BACKGROUND
The continuing appeal of manual memory management highlights the research problem we aim to solve: how can languages give programmers the level of control o ered by manual memory management, while maintaining memory safety?Two broad research streams tackle this problem, one based on regions and one based on ownership.
Regions.Gay and Aiken [1998] introduced explicit regions for managing memory in C-like languages: objects are allocated in regions; and entire regions of objects are deleted in one operation, rather than deleting objects individually.A later version of this scheme added annotations to indicate that a pointer refers to an object in the same region, in an enclosing region, or is not allocated via the region system [Gay and Aiken 2001].Utting [1995] had previously shown how regions could help local reasoning, based on the "collections" or "local stores" used to di erentiate pointers in Euclid [Lampson et al. 1977].
Rather than using explicit, programmer-visible regions, Tofte and Talpin [2004;1997] demonstrated how Milner-style inference [Baker 1990] could be extended to implicitly allocate objects to regions, and allocate and deallocate those regions, without either explicit rst-class regions, or additional annotations on programs or types.Their MLKit [Tofte et al. 2021] runs ML programs using stack allocation, as the regions are allocated last-in, rst-out.Because these inferred regions are implicit, the region structure does not capture a programmer's intent about how and where memory should be allocated and freed.MLKit remains under continuous development, in particular showing how regions can be supported in a straightforward monadic style [Fluet and Morrisett 2006], and integrating generational-style GC within regions [Elsman and Hallenberg 2021].
Safe region allocation was then tested at scale in Cyclone [Grossman et al. 2002] an extension of C with an explicit region construct, rather than using inference.Like the MLKit, Cyclone regions were originally stack based; later versions also adopted support for unique pointers and reference counted objects to permit deallocation of individual objects inside a region, at the cost or introducing memory leaks due to cycles or failure to deallocate a dropped unique pointer [Hicks et al. 2004].Both MLKit style implicit / inferred regions, and Cyclone explicit / annotated regions can be modelled by a common core calculus based on linear references to explicit, rst-class regions [Fluet et al. 2006].
Ownership.Work on object ownership e ectively begins with Hogg's Islands [1991] and a general recognition of the need to control topologies of programs [Hogg et al. 1992] in languages where object identity (i.e., dynamic allocation, mutable state), encapsulation, and even "automatic storage management" are taken as essential design principles, rather than accidental optimisations [Ingalls 1981;Lehrmann Madsen et al. 1993;Lieberherr and Holland 1989].
Based on "Flexible Alias Protection" [Noble et al. 1998], ownership types [Clarke 2001;Clarke et al. 1998] o er compile-time enforcement of pointer encapsulation, including the property that, considering paths through the object graph, an "owner" object should dominate all other objects that programmers intend to encapsulate "inside" it [Potter et al. 1998].Leveraging "owners-as-dominators", extensions to ownership types have been applied to encapsulate object invariants [Müller and Poetzsch-He ter 1999], record conformance to software architecture [Aldrich and Chambers 2004], localise program e ects [Clarke and Drossopoulou 2002], scope object cloning [Li et al. 2012], ensure actor isolation [Clarke et al. 2008;Gruber and Boyer 2013;Srinivasan and Mycroft 2008], prevent data races [Boyapati and Rinard 2001;Gonnord et al. 2023], support safe parallelisation [Bocchino 2011;Francis-Landau et al. 2016] or ensure data is only accessed under a mediating lock [Flanagan and Freund 2000]-the rst fteen years of these e orts are surveyed in [Clarke et al. 2013].Ownersas-dominators leads directly to applications in memory management, as deleting a dominating node from a graph, by de nition, must also delete every node it dominates.This was rst demonstrated in SafeJava [Boyapati et al. 2003] using ownership types to compile straightforwardly annotated programs to the Real-Time Speci cation for Java [Bollella et al. 2003], which supports ne-grained control over memory via explicit dynamically-scoped regions.
Distinguishing between the inside and outside of an encapsulated object lets languages generalise traditional pointer-based uniqueness to external uniqueness [Clarke and Wrigstad 2003], where an object may have only one pointer from the outside, but any number of pointers from its inside.As with regions for unique objects, an externally unique object can be represented as the sole object in a region; however for external uniqueness, the object's region can also contain one or more enclosed subregions in turn containing the object's insides.External uniqueness is almost as strong as regular uniqueness [Wrigstad 2006], and in particular makes it easier to change objects' ownership, or dually, to transfer objects between actors or independent threads [Clarke et al. 2008;Gordon et al. 2012;Haller and Loiko 2016;Haller and Odersky 2010].
Rust.Regions and ownership have been brought together recently in the design of Rust, combining control of memory use, safe concurrency, and excellent compiler error messages [Hu 2020;Klabnik and Nichols 2019;Krill 2021;Turon 2015].Rust essentially inherits Cyclone's and MLKit's regions, but strongly integrated with uniqueness.In particular, only values which are uniquely referenced or passed by copy are mutable.Nested uniqueness brings nested ownership: a unique value is owned by its storage location, ensuring that when an owner is deallocated, all the memory owned by that owner can also be deallocated.
To make programming in Rust practical, Rust allows unique values to be borrowed without nullifying their source: a unique mutable reference can be passed up the stack without losing uniqueness [Boyland 2001] or traded for multiple read-only borrowed references [Wadler 1990].To ensure absence of dangling pointers, Rust tracks lifetimes of borrowed references and ensure that a "longer-lived" (or enclosing) object can never point to a "shorter-lived" (or encapsulated) object.This borrowing semantics is reminiscent of fractional permissions [Boyland 2013].Through uniqueness, Rust imposes a multiple-reader/single-writer concurrency model [Lea 1998].
The strict rules surrounding ownership and borrowing, and compilers' inability to accept safe programs that they "cannot understand", make Rust hard to learn and to use correctly [Abtahi and Dietz 2020;Blaser 2019;Coblenz et al. 2022;Jung et al. 2020;Qin et al. 2020;Spencer 2020]-to the point where the di culty of implementing rst-year data structures (such as doubly-linked lists) has now become an Internet trope [Beingessner 2019;Cameron 2015;Cohen 2018;ndrewxie 2019].When faced with these problems in practice, Rust programmers either escape into unsafe Rust or revert to the birthplace of aliasing, using integer array indices, FORTRAN style [Bendersky 2021].

REGGIO REGIONS
In this section we describe the central concepts of the Reggio region system: regions, the region topology, operations on regions, the single window of mutability, and the properties of the region system.But rst, let us overview the goals of this work.

Motivation
The design decisions in this paper are motivated by our primary goal: (G1) Controllable and Predictable Memory Management Costs.It should be possible for a programmer to reason about and control the impact of memory management on performance.
Our approach is to divide a program's heap into isolated regions and make each region an isolated unit of memory management.Concretely, we set the following ve sub-goals: (G2) Mix-and-Match Memory Management.A region is free to manage its own memory however it likes, irrespective of any other regions in the program.Thus, a programmer is free to pick a memory management strategy suited to the needs of particular operations.(G3) Incremental Memory Management.Performance of memory management in one region should not be a ected by activities in another region.Thus, ne-grained partitioning gives ner cost-control.(G4) Zero-Copy Ownership Transfer.Ownership transfer between regions must be possible without copying objects.Thus, ne-grained partitioning does not have a hidden expensive cost, and heap topology can be modi ed cheaply.(G5) Concurrent Memory Management.Timing of memory management in one region should not be contingent by activities in another region.Thus, a programmer can initiate an operationmemory management or not-without having to wait, or forcing a wait upon any other part of the program.
(G6) Safe Concurrency.A thread that has access to a datum may access it freely without any need for synchronisation, and with a guarantee of data-race freedom.
Because this paper does not deal explicitly with concurrency, we will refrain from discussing the last two goals until §8.1.

High-Level Overview
We distinguish between mutable and immutable objects.In this paper, we are mostly concerned with the former.Immutable objects do not live in regions, and can be accessed freely in a program.In contrast, every mutable object belongs to a particular region.In certain circumstances, mutable objects may be made temporarily immutable.To avoid confusion, we will sometimes use the phrase permanently immutable to denote objects whose mutability is irretrievably lost.

Regions and Region
Topology.A region is a set of objects whose memory is managed together.At any moment, one of these objects is designated as the bridge object.A region can be opened or closed.Closed regions are isolated from the rest of the program which means that with the exception of the bridge object, objects in a closed region are only referenced from within the region.Bridge objects are externally unique [Clarke and Wrigstad 2003] so they may have an additional, single external incoming reference.
The only outgoing references from objects in a region are either to immutable objects or to bridge objects of other (nested) regions.Thus, a program's region topology forms a forest, and moving the external reference to the bridge object changes the topology.The topology of references within regions is unrestricted: any object can point to any other within the same region.Fig. 1 illustrates the isolation of the region (bean-shaped boundary).Object is the current bridge object of the region, denoted by drawing it on the boundary.Also and could serve as the bridge object.References → and → are not permitted as they break isolation.The reference from → is permitted as is immutable.The reference → is permitted because is the current bridge object.Immutable objects do not live in regions and may only refer to other immutable objects.Therefore, the reference → is not allowed.Last, bridge objects may only have one incoming reference from outside the region, so no more references to are allowed from outside of , regardless of their origin.

Regions and Memory
Management.Every region manages the memory of its objects in isolation, and according to a strategy picked by the programmer speci cally for that region at the time of its creation.Code inside of a region is agnostic to how memory is managed, meaning that a library can leave such decisions to its users.
When an external reference to a bridge object is dropped, the entire corresponding region can be free'd along with any nested regions.In Fig. 1, dropping → makes all objects in region unreachable as external references to its objects (e.g., → ) are not permitted.Thus, they can be collected immediately.

Single Window of Mutability.
A region must be explicitly opened to be accessed, and must be closed before it can be opened again.The open regions form a LIFO stack.The top region is active and the remaining regions on the stack are suspended.An active region permits allocation, deallocation and mutation of its objects.When a region is suspended, neither allocation, deallocation nor mutation is permitted in it.Making a region active temporarily weakens its outgoing encapsulation: its objects are permitted to reference objects in the suspended regions further down the stack.Suspending a region conversely weakens its incoming encapsulation: its objects can be referenced by the regions further up the stack.Table 1 overviews the allowed actions depending on a whether a region is active, suspended or closed.When the active region is closed, it is popped from the stack, and the new top region goes from suspended to active.Because closed regions are not permitted outgoing refereces, any references to objects in open regions must be invalidated.
To the active region, the suspended regions appear as a single temporarily immutable region whose objects can be referenced as long as the active region remains on the stack.Programmers can thus trade mutability for access, and any object can be temporarily accessed from anywhere, provided the containing regions are opened on the stack in a permitting order.
In Fig. 1, to open we must rst open ′ to access the reference → .Opening through this reference suspends ′ , making and temporarily immutable, , , and mutable, and permitting → .With the topology of Fig. 1, we cannot open and ′ in a way that permits the creation of → as is immutable when ′ is suspended, and is not accessible when is closed.To do so, we must change the topology by moving → out of ′ , e.g., to a stack variable or other region.
In combination, the design decision to only permit mutation in one region at a time, the LIFO order of the region stack and the inaccessibility of closed regions facilitates reasoning about sidee ects.The main motivation, however, is to control the costs of memory management.As we shall see, direct overheads related to memory management such as maintaining reference counts or tracing object structures are only applicable to active regions.3.2.4Navigating Through the Region Forest.Due to the single window of mutability, programs require explicit navigation through the region forest.The left sub gure of Fig. 2 shows Fig. 1, denoting regions' states by colour when ′ is active and is closed.The white box denotes the stack frame of ′ with its local variable(s).If we proceed by opening we arrive at the right sub gure of Fig. 2: a new top frame is created inside containing its own local variables, with y holding a reference to ; ′ is suspended and active, and the window of mutability is moved from ′ to .The reference → shows the weakened isolation allowing outgoing references from and incoming references to ′ .To continue, we may close or open any reachable region ′′ .The former will invalidate any references from to ′ since these would violate isolation.The latter gives mutable access to ′′ and suspends , and permits references from ′′ to both and ′ .3.2.5 Swapping the Bridge Object.Any object in a region can serve as the bridge object.While a region is open, any object can be designated as the new bridge object, and we make this choice visible when the region closes.Thus, it is possible to e.g., create a region with a stack where the bridge object is always the top link.If we wish to create an external iterator to the stack, we can create an iterator inside the region, and make the iterator the bridge object for the duration of the iteration, and then switch back again.Fig. 3 shows the situation pictorially.
3.2.6Merging and Freezing Regions.A closed region's contents may be merged into another region by dropping the uniqueness of the bridge object.For clarity, we use an explicit merge operation.Fig. 4 shows merging ′ into , which moves the objects in ′ into and creating the (now legal) → reference from the variable x, after which ′ ceases to exist.Merging a region (source) into another (sink) moves all regions directly nested in the source to the sink, but does not merge those regions into the sink (see ′′ in Fig. 4).
Permanently immutable structures are created by constructing a mutable region and then turning its entire contents immutable through an explicit freeze operation that operates on closed regions.In contrast to merging, freezing a region also freezes its nested regions (see Fig. 4).This preserves the property that immutable objects may only reference other immutable objects.Freezing dissolves region boundaries, making the frozen objects freely accessible to objects in all regions.

REFERENCE CAPABILITIES FOR STATICALLY ENFORCING REGION ISOLATION
We now introduce a type system that statically enforces region isolation according to the encapsulation and e ects columns of Table 1.A region is opened through an enter operation that takes a reference to bridge object and a lambda, executes the lambda inside the region passing it the bridge object as argument, and then closes the region.Its companion operation explore opens a region in a suspended state (while still suspending the former active region).
Our type system uses reference capabilities which decorate all types in a program: mut denotes an intra-regional reference to an object of type (r, c, e, and m in Listing 1); tmp is like mut , but the lifetime of the object is bound to the enter/explore scope; imm denotes a reference to a permanently immutable object of type (i and o); iso denotes an externally unique reference to a bridge object of type of a closed region (a); paused denotes a reference to an immutable object in a suspended region (z in Fig. 2).
On assignment, the lhs capability and the rhs capability must be the same.merge has the signature iso→mut and freeze has the signature iso→imm.To enter or explore, an iso is needed.All expressions are typed from the point of view of the currently active region.A eld f declared with a mut capability will only appear as such when the object containing f is in the active region.If 's containing region is suspended, f will also appear as suspended; if 's containing region is closed, f is not even visible to the program.This is handled by viewpoint adaptation (c.f., §4.1)., using the most recently added link as the bridge object.Subfigures 4-6 construct an iterator, for the list, and make it the bridge object.We subsequently use the iterator to iterate to the link and unlink it, before dropping the iterator and making the bridge object (and with a garbage iterator whose removal is determined by the region's memory management strategy).An invariant in our system is that aliases to an object that are accessible simultaneously have the same capability.This design is motivated in-part by region isolation and in-part by a desire to keep complexity down in this presentation.For example: If a mut and paused could alias, the immutability of the latter would be weakened to read-only.If paused and imm could alias, it would violate the temporary vs. permanent nature of their immutability.(This restriction could be relaxed to permit some aliasing across the mutable capabilities and aliasing across immutable capabilities.) 1 // Freeze creates immutable objects, c.f. §3.2.6 Constructing Fig. 1.Listing 1 shows code that creates the regions, objects, and (permitted) references of Fig. 1.Line 4 creates the region.On Line 2, the immutable object is constructed by creating a region and freezing it.The object is created similarly.Its reference to does not break region isolation as is immutable.We could get rid of the freeze on Line 2 since Line 3 moves into 's region and then freezes the entire structure.
When a region is created, it is closed and empty, except for its bridge object.To populate as in Fig. 1, it must rst be made active.The enter keyword is used to open a region and making it active.It takes a unique reference to a bridge object as its argument.Lines 6-11 of Listing 1 show the use of an enter block to open the region to allocate and mutate its objects.(The code executes with region active and region ′ suspended.) Entering a region moves control inside it and places a mut reference to its bridge object in a variable on the stack (r) that can be used to call methods, or obtain and store references to other objects in the region.Exiting the enter block (after Line 11) closes the region, and moves control back to the previous region.While a region is open, the external reference to its bridge object is buried [Boyland 2001], meaning it is not accessible to the program.
Upon exiting the enter block, all variables referencing objects in the now-closed region (c, e, and r) are invalidated, save for the reference to the bridge object in a.Any temporarily permitted references to objects in suspended regions, such as → in Fig. 1, will be invalidated as well.(We will show how this is enforced statically in §4.3.)

Controlling E ects through Viewpoint Adaptation
We rely on viewpoint adaptation [Dietl et al. 2007] to capture how a reference's type changes depending on its enclosing region's relation to the active region.Viewpoint adaptation means that the type of an object may appear di erently depending on from where it is accessed.For example, when accessed through a variable of type imm, a eld declared with type mut appears as imm.(This particular case ensures that turning a unique reference to a bridge object immutable will propagate the immutability to the entire region and nested regions.)Table 2. Viewpoint adaptation.If the capabilities of x and f are and , then the capability of x.f is ⊙ = , which we read as " sees as ." For †, c.f., §4.3.The meaning of ⊥ is inaccessible; For ⊥/iso see text.
Capabil-Capability on f ity on x mut tmp imm iso paused Viewpoint adaptation also changes the types of variables captured by an enter or explore block to propagate suspension.Captured isos retain their iso-ness rather than become paused.Table 2 shows the viewpoint adaptation rules.The meaning of ⊥/iso is that an iso location is inaccessible through a mut, tmp or paused, unless it is swapped, buried or borrowed (c.f., §4.2).
To illustrate viewpoint adaptation, consider A enter x { y ⇒ B }.In scope A, let the variables x and v have the types iso 1 and 2 respectively.In scope B, x is unde ned and y is introduced with type mut 1 .This re ects the region pointed to by x going from closed (denoted by x being iso) to active (denoted by y being mut).Moving control from A to B suspends the region active in A (denoted there by mut and tmp).Thus, in scope B, the type of v is (paused ⊙ ) 2 .Through a paused reference, objects in the same region are paused.paused and imm references stay paused and imm respectively (permanently immutable is stronger than temporarily immutable).iso references stay iso.This permits opening nested regions of a suspended region.
If = iso, then v.f is not typeable in neither A nor B as iso ⊙ ′ = ⊥, regardless of what ′ (the capability of f) is.This is as expected, as iso's cannot be dereferenced (they must be enter'd).

Region Isolation and Bridge Object External Uniqueness
Regions which are closed or active only have a single incoming reference, which goes to the bridge object.Thus, when a region is closed, it can be moved in and out of other regions by moving its single incoming reference.When a region is opened, its containing region is suspended, which means that the object containing eld holding the reference to the bridge object is paused so the eld cannot be reassigned.Thus, regions cannot move while open.Finally, when a region is suspended, incoming references are permitted from the stack and heap of subsequently opened regions (c.f., § 4.3).As regions are opened using a lexically scoped construct (enter or explore), regions are opened and closed in LIFO order.This means that when a suspended region becomes active again, the permitted incoming aliases that could be declared in the block have gone out of scope, and the region's bridge object is again the only incoming alias.
As shown in Table 3 uniqueness of bridge object references is maintained by a combination of swapping, burying, and borrowing.
Table 3. Maintaining uniqueness of bridge object references.

Swap [Harms and Weide 1991]
Reading a mutable variable containing an iso requires that its contents is replaced.For example, y = x is not permitted when x is iso.However, y = x = is, which replaces the value of x by the value and moves the previous value of x into y.

Bury [Boyland 2001]
Reading a let-bound variable with an iso invalidates the variable.For example, foo(x, x) is not permitted when x is iso as the second use of x cannot be typed.

Borrow [Wadler 1990]
Dereferencing an iso requires opening its region, where aliasing of the bridge object is unrestricted, and region isolation protects aliases to the bridge object from escaping.
Entering a region borrows and/or buries the variable or eld referencing the bridge object.In the case of a stack variable, the variable is buried to prevent the region from being multiply opened.In the case of a eld, we instead resort to a dynamic check of the region's state.If the region is closed, it may be opened.If the region is already open, an exception is thrown.

Temporary Objects Allow References to Suspended Regions on the Heap
As exempli ed already, we permit local variables to store references to objects in a suspended region (e.g., z in Fig. 2).This is sound as objects in suspended regions are immutable, and because local variables in the active region are guaranteed to go out of scope before a region opened by an enclosing enter or explore becomes active (and thus mutable), as explained above.
Because objects with tmp capability are created in, and bounded by, a lexical scope, they have the same lifetimes as local variables declared therein.Therefore, we can grant the same permissions to store references to suspended regions to tmp objects.We permit accessing paused and tmp elds through a tmp, but not through a mut (as shown in Table 2).Permitting a mut object to store a suspended reference could lead to a breach of region isolation (see §4.5 for an example).Thus from

Storage Locations, Strong Updates and Bridge Object Swapping
We unify the treatment of mutable variables (denoted var as opposed to let) and elds through a storage location abstraction (similar to a pointer to a variable or eld in C).
Storage locations are typed Store[ ′ ] where is the capability of the frame or object containing the location and ′ is the capability of the value stored at the location.
We add a new capability that we call var for use in mutable local variables.var di ers from tmp in that it supports strong updates.Its viewpoint adaptation rule is var ⊙ = ("var sees as ").
As shown in Listing 2, a mutable local variable x holding a -typed value has the type var Store[ ].Storage locations are subject to the normal rules for viewpoint adaptation, so opening another region when x is already in scope will change the type of x to paused Store[ ].We introduce a dereference operator * and an update operator := to access the contents of a storage location.A storage location must be mut, tmp, or var to be updated.We apply viewpoint adaptation to type the result of dereferencing.On Line 7, *x has type (paused ⊙ mut) Cell, i.e., paused Cell.We support changing the bridge object of a region-including changing it for an object of a di erent type-by presenting the borrowed bridge object reference internally in the enter block as a var storage location.(Note that it is not possible to update the bridge object in an explore as it opens the region as suspended.) Line 6 shows that changing the bridge object to an object of another type is possible by simply assigning to y.Strong updates of elds are not possible, and this is handled by using tmp Store[. ..] instead of var Store[. ..] to type a bridge object reference borrowed from a eld as opposed to a stack variable.

Types Enforce Region Isolation and the Single Window of Mutability
Region isolation means no references into active or closed regions from other regions (modulo unique references to bridge objects) or from immutable objects, and no outgoing references from closed regions to open regions.Let's see how our types enforce this, by looking at in Listing 1.
The newly created region (Line 4) is isolated as iso constructors only accept iso's and imm's as arguments.Right after creation, is closed, and its only external reference is iso.Since iso's cannot be the receivers of method calls or eld accesses, we cannot read or write internal objects in , so we cannot create the illegal references → or → .
When is active (Line 6-11), all previously suspended regions stay suspended (none in Listing 1), and are joined by the current region ( ′ ).This is captured by viewpoint adaptation which changes all variables which are mut, tmp, or var in the enclosing region to paused.This prevents these variables from being updated, and reading them yields paused references.Field updates via paused or imm references are not allowed, and method calls on such references require that the method's self type matches the external view, meaning any callable method cannot perform a eld update on self (or call such a method).Thus, we cannot create → or → .
As permitted by our de nition of region isolation, we may store paused references in the elds of tmp objects in while is active (e.g., → if is created as tmp on Line 7).These references will be invalidated when closes as tmp references can only be stored in variables local to the enter block (since mutable variables in the enclosing scope have been suspended), or in other tmp objects (i.e., → → is an impossible path if is tmp, as the object is mut by de nition).
If we did not invalidate references into suspended regions such as → , we could circumvent region encapsulation.For example, imagine closing (without invalidating → ), then closing ′ while moving out of ′ .Then reopen without rst opening ′ .Now → would constitute a reference into the internals of a closed region, thereby breaking region isolation.
Inside an enter or explore block the enclosing scope is immutable.Together with region isolation this gives that only one region at a time is mutable, i.e., a "single window of mutability".
Last, region isolation means that reassigning the pointer to the externally unique bridge object e ectively changes the region topology of the heap (G4).

THE USE OF REGIONS FOR MANAGING LIVENESS
We have sketched how our type system enforces region isolation and the single window of mutability.In this section, we will show how this translates to costs for managing liveness when considering memory management in isolated regions.
Fig. 5 shows a heap consisting of regions 1 to 6 .Presently, the program has entered 1 , 2 , 3 and 4 in that order.Ignoring method call indirections, we can imagine a corresponding program shape starting in 1 : The region stack is thus [ 4 , 3 , 2 , 1 ] where 4 is active.Region isolation prevents references from objects in region to objects in region if < with the exception of the bridge object references: x → , → , and → .The rst of these is made inaccessible right after the rst enter by making x unde ned in R 2 and nested scopes.While 4 is open, 2 and 3 are suspended, meaning the elds holding → and → cannot be reassigned (static check).Furthermore, they cannot be dereferenced (i.e., opened) since the regions are already open (dynamic check).Thus, while 4 is open, the incoming references to the bridge objects will remain the same, i.e., the path that holds the region alive is stable. 1ecause of this, as long as 4 remains active, liveness of objects in regions 1 -3 and 5 is invariant as no activity in 4 can cause objects in these regions to become garbage.2This does not hold for 6 , as 4 could drop ℎ → to make the entire region 6 garbage.It does hold for → however, since is in a suspended region ( is temporarily immutable).Furthermore, because of the absence of references from 1 -3 into 4 -with the exceptions of the stable bridge object references that are either buried or cannot be re-opened, objects in 1 -3 cannot a ect the liveness of objects in 4 (this is also true for 5 and 6 as they are closed).Thus, we can safely ignore references to objects in suspended regions when managing liveness.For example, if objects in 1 , 2 or 3 are managed by reference counting, we do not need to increment or decrement reference counts when manipulating paused references in 4 .Similarly, if objects in 1 , 2 or 3 are managed by a tracing GC, tracing in 4 does not need to follow paused references.Thus, when managing memory in the active region, we can safely ignore any outgoing references, and so it is possible for 1 -3 to use di erent strategies, unbeknownst to 4 and irrespective of how 4 manages its memory (G2).The only references to objects in 5 and 6 that are possible (given that the regions are closed) are to the bridge objects and .Aliases of are not possible in 4 as iso references are unique, and we cannot transfer references out of an immutable .However, we do need to track liveness of the reference to , which is possible to do statically e.g., as in Rust.
From the reasoning above it follows that liveness of objects in 4 can be determined by looking at objects in 4 alone, meaning that the costs of memory management are determined by the contents of and activity in 4 (G1).This makes it possible to collect garbage in just 4 (G3).

PROGRAMMING WITH REGGIO REGIONS
As an example of how regions enable predictable memory management performance, consider the server application "Po" with the following key characteristics (see Listing 3 for skeleton code): (1) The server serves incoming requests.Tasks that process requests are short-lived and their side-e ects are typically in the form of data stored in a database.(2) Responses to requests are served from data in an in-memory key-value store implemented as a skip list.This storage will shrink and grow continuously during execution.
(3) Values in the store can have a complicated graph-like structure (e.g., they may contain cycles).
We now explain how we can express this, and compare with Cyclone, MLKit, Pony, and Rust.
Processing Requests.To manage allocations necessary to process a request, each request is wrapped in a region (Line 16, Line 49).These regions use arena allocation: allocations in the region persist until the region itself is deallocated.This gives cheap bump-pointer allocation and fast deallocation of the entire region once the response has been computed.If a request must be processed by di erent threads, this can be done cheaply due to the transferability of iso's.If some requests turn out to require considerable processing time, their corresponding regions might switch away from arena allocation at the small cost of changing one annotation at the creation site of the region(s).
Comparison.Cyclone's LIFO regions are perfect for this purpose (as are MLKit's, provided that the inference engine infers according to intentions, or better).While Cyclone does not support switching from arena allocation, it does permit manually managed reference counts or unique objects that can be manually deallocated during the arena's lifetime.Pony only supports memory management on a per-actor level, (using tracing GC).We could make each request an actor, but enter new iso<Arena> Unit { _ => // create new tmp region, the one created on Line 16 is not accessible here let q : mut SQL_Query = Factory::make_can_delete_query(id) // q cannot refer to kv because it is mut let r : tmp SQL_Result = Backend::execute_query(q) // because it is tmp, r could refer to kv if it needed to if (!r.OK) return new iso Failure(...) // moves out of tmp and kv regions and merged into r on line 21 } // arena goes out of scope, allocs on lines 50, 51 are free'd return enter kv.remove(id) { v => new iso DeleteOK(v) } // Creates garbage in kv, drops a Value region } Listing 3. Skeleton code for Po.To save space, we permit constructor arguments to iso objects to take mut arguments (Lines 38,45,52,and 54).This is safe and can desugar to an extra enter.These lines all create an object that escapes the active region and is merged into the enclosing region, making them mut in r (append expects a mut argument).The ServerSocket is created elsewhere.It likely does not use arena allocation since it allocates on each turn of the loop (Line 13).If it did, those allocations would only be free'd on Line 31.The default annotation on new iso is <Arena> (c.f., §8.3).Notice how code is agnostic to how memory is managed.
it would have to communicate asynchronously with all surrounding state.Rust does not support regions, but could e.g., build a unique object holding unique values and thread this object through computation.While more complicated, Rust's values would have individual lifetimes.
Key-Value Store.The key-value store is implemented as a single region containing a skip list (Line 5, could as well be a hash table, B-tree, etc.).As it is a large long-living data structure of objects with di erent lifetimes, arena allocation is not a suitable strategy.Furthermore, if the resources (values) stored in the skip list are costly, reference counting is a good choice as it allows the resources in the list to be recycled immediately when they become garbage.Alternatively (the path we chose) is to make each element a region of its own, with independently managed memory (Line 1).
If the store is large enough to warrant parallel accesses, it can be divided into several smaller regions with one list each.For a compile-time guarantee that no reference count manipulations in the skip list are needed during lookups, lookups can be implemented using explore rather than enter as the former opens the skip list region directly as paused (Line 23, and its dynamic extent foo()).Note that since the elements in the skip list are regions of their own, these can be entered separately and thus mutated, even if the list structure is immutable (Line 35).
Comparison.Cyclone's dynamic regions are a good t for the key-value store.The objects that make up the skip list would require manual reference counting, which can be laborious.Failure to properly manage reference counts in Cyclone will also lead to "memory leaks" which will not be reclaimed until the region holding the store is (manually) destroyed.Pony can wrap the skip list inside an actor with an asynchronous interface, and manage its memory using tracing of the entire list leading to more time spent tracing memory and more oating garbage.Skip lists (hash tables, B-trees, etc.) cannot be constructed in Rust without judicious use of unsafe.Safe Rust's reference counting does not relax its ownership rules, so mutation of aliased values is not permitted.
Values in the Store.Finally, the elements in the store are suitable for either reference counting or tracing GC because of their graph-like structure (Line 42 delegates this decision to a factory method).GC is especially favoured in the (possible) presence of cycles which are expensive to detect with reference counting [Jones et al. 2016].
Comparison.Later versions of Cyclone and MLKit support a global arena where memory is managed using tracing GC.Thus, all elements in the store contribute to pressure on the same GC, and GC requires tracing through all elements to free garbage objects in one element.MLKit's region propagation requires all elements to be put in a single region.Pony can handle this pattern by making each element an actor, which makes each element aliasable, and use an asynchronous interface.Finally, Rust will not be able to express and manage lifetimes of these structures automatically.A combination of unsafe and manual memory management is needed.
Design Thoughts on Explore vs. Enter-And Invariants.The explore construct is essentially syntactic sugar for two nested enter blocks, the outermost entering the region to be explored and the innermost entering a fresh region: explore x { y => . . .} desugars to enter x { y => { enter (new iso Unit) { _ => . . .} } The rst enter activates the region, and the second suspends it.The new region (new iso Unit) is independent from the rest of the program.As it is active, it serves all allocations that appear inside the explore block (as suspended regions do not permit allocation or deallocation, Table 1).What explore guarantees that unprincipled nesting of enters does not, is that the explored region was not mutated before suspended.Conceptually, this is a big di erence as we will explain next.
Similar to object invariants, we expect invariants of a closed region to hold at the time of opening.While active, invariants may temporarily be broken and then restablished before the region is closed.
As a nested enter can reference any enclosing region, it will be able to observe any invariants broken by mutation following the opening of the enclosing regions.By opening regions directly in a suspended state, explore ensures that region invariants continue to hold.We are considering using a separate capability to capture this statically.We are also considering an "eager" explore construct that opens a region along with all its subregions as suspended in one fell swoop.This would avoid the need for explicit opening of subregions thus further simplifying working with immutable objects.The cost is more complexity in the type system.Time will tell whether this complexity is warranted or not.
With respect to memory management, explore allows opening a region for reading, and navigating through it, without any memory management overhead as the region's object structure is invariant and there are neither allocations nor deallocations in the region.Navigating Regions.Listing 4 shows a zip computation involving three unrelated regions.Using explore, we open the sta and reviews regions to make them temporarily immutable and their contents accessible.Finally, we open the zip region using enter.This makes the region active which allows allocation of the two iterators on Lines 8 and 9 and any allocation needed by the call to append on Line 11 to extend the list.It also allows the mutation in the next() calls to advance the iterators, and the mutation necessary to add the new pair to the zip list.Allocating the iterators inside the same region as their corresponding list is not useful as it would make the iterators immutable on Lines 11 and 12.This would cause the program to not typecheck-as the next() method needs to update the iterator, it needs to be called on a mut or tmp receiver.Since the iterators need to store paused references, they must be tmp (c.f., Table 2).This can be handled in iterator() by overloading on the self type, letting the implementation with paused self-type return a tmp reference.Elements are immutable so e.g., next() returns imm.Reggio's Borrowing Capabilities.Traditional borrowing as originally introduced by Wadler [1990], explored deeply by e.g., Boyland [2001] and Boyland et al. [2001], and popularised by Rust relaxes uniqueness of a value in a well-de ned lexical scope.We can express a similar form of borrowing through the type paused Store[iso T], i.e., a reference to a storage location in a suspended region storing a reference to a closed region.Such a reference (e.g., x) can be shared freely inside a single thread, allowing it to ow to a place where it can be opened (enter *x) with mutation rights, including swapping the bridge object (as long as the new bridge object is a subtype of T).

FORMALISING REGGIO
We formalise Reggio through two interacting languages: region and command, and their respective semantics.The former controls all accesses to memory (loads and stores), allocation of objects in regions, creating, merging, freezing-and importantly entering and exiting-regions.The most important properties of the region language is expressed as a topology invariant (c.f., §7.5).The command language is essentially "what the programmer wrote".This separation makes it possible to specify e.g., under what conditions a store is valid, irrespective of what caused the store.
During execution, the command language emits e ects which the region language performs.The static semantics of the command language ensures, modulo one dynamic check, that the topology invariant is preserved.We present most of the rules of the region language and key type system rules.Additional details are available in a technical report [Arvidsson et al. 2023].A con guration in the region language has four components: a LIFO region stack RS and the sub-heaps of open regions op , closed regions cl , and frozen regions fr .The latter is an unimportant simpli cation (conceptually, only mutable objects live in regions).A heap is a collection of disjoint regions .Opening a closed region moves it from cl to op and pushes a new stack frame on top of RS.Closing the top-most region in RS returns it back to cl .Freezing a region moves it, and all regions reachable from it, permanently to fr .As an example, consider the region topology depicted in Fig. 5.We can write down the corresponding con guration as ⟨RS, op , cl , fr ⟩ where RS = RF 4 :: RF 3 :: RF 2 :: RF 1 ::

Dynamic Semantics of the Region Language
For the region sub-heap , if is open (i.e., part of op ), RF is its region frame (depicted in Fig. 5 as a white box) that holds the stack variables created in the scope of the corresponding enter block.
Opening the closed region 6 would push a new frame RF 6 above RF 4 in RS, and move 6 from cl to op .Similarly we could freeze (merge) 6 , which would move it from cl to fr (remove it from cl and merge it into 4 ).In this model, an inter-region reference into an open region is permissible i it points downwards in the region stack (from left to right according to RS), or it is the unique (iso) reference through which the region was opened.The LIFO region stack constitutes a "path" through the region forest that corresponds to the opening order of enter blocks (c.f., the region LIFO order in Fig. 5).Thus, in Fig. 5, any reference from 4 to 3 is permissible (as long as we do not close 4 ), while a reference from 2 to 3 must necessarily be the reference from object to object .We model explore as nested enters.
A region stack frame RF contains a region identi er , a temporary store for objects whose lifetimes are bounded by the scope of the region's enter block (values with capability tmp), and a map from variable names to values , representing the local variables in that enter block (we model destructive reads of a variable by remapping it to undef, at which point reading again will lead to the program getting stuck).A region is a tuple of a (unique) region identi er and a store containing the objects in that region.
Objects are identi ed by .Values are object identi ers tagged with a capability .Stores map object ids to objects which store their class tag # and elds (for simplicity we reuse the same as for local variables, although a eld will never contain undef).
The command language communicates with the region language via e ects.The relation rcfg E − −− → rcfg ′ should be understood as performing the e ect E in rcfg, resulting in rcfg ′ .E ects include entering and exiting a region, loading a value from an object store, writing (swapping) a value for another in an object store, merging or freezing a region, etc.We now describe a selection of rules for these e ects.

region-load
x fresh In every e ect, the rst parameter should be understood as the name of the variable where the results should be stored.For example, the e ect load ( , . ) is handled in rule region-load by binding the value of eld .to variable .First, the value of is looked up in the top stack frame as ( , ).The object id is used to nd the corresponding object in the con guration-it may be stored in the subheap of an open or frozen region, or in one of the temporary stores on the region stack.The capability is used for viewpoint adaptation of the (capability of the) value of eld in before it is inserted into the top frame.
region-swap-temp  A use is a potentially destructive variable access ( or drop , see Fig. 9).Rules region-swap-temp and region-swap-heap handle the cases where the object being assigned to is in the temporary store or on the heap.In both cases, we perform the use (which may make a variable invalid) with the helper function get (see Fig. 8).
We then proceed just as when loading a eld, but nish by updating the object being assigned to and update its containing store .Note that assigning and loading mutable variables are special cases of the swap and load e ects since we model mutable variables as single-eld objects.
region-alloc-heap-mut region-alloc-heap-iso Allocation on the heap is caused by the e ect halloc( , , , use 1 ...use ), which instructs the region language to heap allocate a new object with elds initialized according to use 1 ...use and bind it to the name .The capability denotes whether to allocate in the current region (region-allocheap-mut) or in a new region (region-alloc-heap-iso).Since each use is a possibly destructive variable access the ordering matters.We begin by performing these one by one with the local variables 1 .Each value is paired up with the corresponding eld of the class and put into an object .We then add at location to the subheap of the currently active region, or add a new region ′ in the closed regions containing only the object at location .Finally we bind the object to in the top frame with capability mut or iso.We omit the rule region-alloc-temp which allocates objects with capabilities tmp or var in the temporary store of the currently active region frame.
The key rules of the region language govern entering and exiting a region.
region-enter-ok region-enter-ok shows successfully entering a region ′ through its bridge object ′ stored in the eld of the variable .(This operation can fail if ′ is already opened.This will not change the state in the region language, as seen in region-enter-fail, and it is up to the command language to choose how to handle this: by exception, having a construct like if-enter-else, etc.For simplicity, the command language steps to a failure state.)The enter e ect supplies four things: the name and capability of the parameter of the enter block, the eld .through which we are entering, and a list of bindings = use denoting the block's captured variables.Going back to Listing 1, the enter block captures i, so the corresponding e ect would include = i.Note that is chosen by the command language and due to variable renaming is not necessarily i. Considering the rule again, we rst use the get helper function to perform the uses.For each resulting value ( , ), we apply the paused viewpoint adaptation when is not iso and create a new mapping of the captured variables.We then get the value of , load its corresponding object and extract the value ′ of eld (our bridge object).On the last line of the premises we check that ′ is an object in a closed region ′ ; this region will be moved into the collection of open regions.We extend with a mapping from to a fresh location ′′ , and nally install this extended into a region frame RF where ′′ is the identi er of a ref cell object pointing to our bridge object ′ .We push this region frame onto the region stack. region-exit-heap region-exit-heap and region-exit-temp describe exiting the region ′ , popping its region frame from the top of the region frame stack.After exiting, the region frame corresponding to will be on the top of the stack and thus active.The exit e ect provides use and which correspond to the return value and the variable to which this will be bound (in the stack frame of ). .′ speci es a location in ′ where a reference to the new bridge object can be found.Finally, .speci es a location where this reference will be written.The only di erence between region-exit-heap and region-exit-temp is where the object pointed to by is located.In the former it is on the heap of some open region, while for the latter it is in a temporary store in the region stack.
For simplicity, we do not implicitly reinstate iso variables captured from the previous region even if they are still valid upon exit from a region (this would be sound, c.f., Listing 4 where variables reviews and zip are reinstated in the top-level scope after line 13).This is without loss of generality as we can return them in an object together with the result and reinstate them manually.

region-freeze
The rules for merging (region-merge) and freezing (region-freeze) are similar.Both perform a use to get an object identi er and nd its containing region among the closed regions.For merges, the subheap of is merged with the subheap of the currently active region, and is bound to as mut.For freezes, all the reachable regions of are moved from the closed to the frozen regions together with , and is bound to as imm.
In addition to allocation in the temporary store, we have omitted the rules for type casts and rebinding of variables.

Static Semantics of the Command Language
The command language is an imperative language in A-normal form.The syntax is shown in Fig. 9.We encode mutable variables of type as ref cells.For uniformity we model these as objects of type Cell[ ] with a single eld val.The if typetest expression is a dynamic type test similar to Java 16 style pattern matching, drop denotes a destructive read, *lval dereferences a eld or ref cell, and var allocates a new ref cell with the capability var and initializes its value from use.For simplicity we provide enter blocks with an explicit capture list, but this could also be inferred from variable use.Types are unions 1 | 2 or , where is a capability and is Cell or a class name .The static semantics is a ow-sensitive type system producing judgements of the form Γ 1 ⊢ : ⊣ Γ 2 ( ∈ ∪ ∪ use).Thus it statically tracks destructive reads and strong updates of unique variables.We discuss a few of the rules below. cmd-ty-use-keep Reading a variable that is not a var or iso is straightforward and introduces an alias (cmd-tyuse-keep).When is var or iso, cmd-ty-use-drop allows reading the variable but unde nes it in the environment as a side-e ect to ensure its single use.When accessing a eld .(cmd-tyderef-field), its type is subject to viewpoint adaptation ⊙ where is the capability of and the type of .Note that viewpoint adaptation disallows reading an iso eld unless is imm, expressing the fact that freezing a region is deep (all nested regions will be frozen as well).A eld . can be updated through assignment (cmd-ty-assign) when the capability of is mut or tmp, i.e., internal references in the currently active region (note that assignment returns the old value of the eld).Viewpoint adaptation is not needed as we are moving values rather than copying them, allowing swapping of iso references.Local variables allow strong updates (cmd-ty-assign-var).As we model them as ref cells we update the type parameter for after assignment.Opening a region through a eld .(cmd-ty-enter) requires that 's capability is open, and 's capability is iso.We create a new environment Γ ′ with the captured variables 1 , ..., , using viewpoint adaptation to suspend the types of all non-iso variables, as well as a tmp ref cell holding the bridge object.We use make_mut ( ) to change the type of the bridge from iso to mut as control is moving inside the opened region.Finally, the enter block may only return iso's and imm's.(Note that entering a region through a eld incurs a dynamic check to see if the region is already open.)

cmd-ty-enter
Opening a region through a var ref cell (cmd-ty-enter-var) is similar to a eld (cmd-ty-enter), but allows strong updates of the ref cell holding the bridge object by retaining its var capability.This allows changing the bridge object's type from within the enter block. cmd-ty-merge The rules for merging and freezing a region (cmd-ty-merge and cmd-ty-freeze) are straightforward.Both demand that the value that we operate on is an iso reference (i.e., bridge object to a closed region), and produce either a mut or imm depending on the operation.

Dynamic Semantics of the Command Language
A con guration { } in the command language is a dynamic expression , which is an extension of by "entered blocks" that propagates syntactically the nesting structure of enters and exits, and thus dynamically tracks the nesting of open regions, and Failure, used to report failed dynamic checks when entering an already open region.The dynamic semantics steps a con guration and produces an e ect of the same kind consumed by the region language.For example, the expression let = * . in produces the e ect load( , .), which tells the region language to load the eld from the object stored in and store it in .

Interaction Between the Region and Command Languages
A complete con guration is a product of the con gurations of the region and command languages.It steps if there is an e ect that steps both of them in tandem: tandem-step A dynamic expression is typed under a stack of typing contexts Γ, corresponding to the nesting of entered blocks.We lift the static semantics of the command language to de ne well-formed e ects: the relation Γ ⊢ E ⊣ Γ ′ statically describes the e ect E and how it changes the typing context.
For example, the static description of the e ect load ( , . ) states that the type of in the top-most entered block is , that has a eld of type , and that the viewpoint adapted type ⊙ is well-formed (c.f., cmd-ty-deref-field).
In order to reason about soundness, we de ne well-formedness of a con guration in the region language as the relation Γ ⊢ ⟨RS; op ; cl ; fr ⟩.A well-formed con guration ensures four things.First, the stack of environments Γ mirrors the region stack RS so that each environment describes the local variables of a region frame in RS.Second, each eld of every object in the con guration contains a value that corresponds to its static type.Third, we have invariants about the reference capabilities: var references are unique, objects in frozen regions only refer to other references in frozen regions, mut references point within the same region and to the heap, tmp and var point within the same region and to the temporary store, paused references point downwards in the region stack, etc.Finally, we have the invariant that the object graph and its regions have the expected topology.We describe this invariant in detail in the following section.

The Topology Invariant
The most important properties of the object (and region) graph are captured in a single invariant that we call the topology invariant (Fig. 10).We express this as a property that holds for any pair of references ref 1 and ref 2 in a well-formed con guration.The helper functions src() and dst() denote the storage location and referee of a reference respectively; reg() denotes the region of an object (or variable); regions() projects the region identi ers out of a set of regions .
For all references ref 1 and ref 2 , either: they are the same reference, e.g., both are stored in the same .or variable (1); both refer to objects in di erent regions (2); or at least one of them is an intra-region reference (3); refers to a permanently immutable object (4); or is a reference outwards in the nesting hierarchy, downwards in the region stack (5).The topology invariant has several important implications: The object graph inside a region is unconstrained (3).The object graph of the permanently immutable objects is unconstrained (4).Temporary objects in an open region are allowed to refer to objects in an open region ′ as long as ′ was opened before (5).Finally, considering the whole invariant, if we have two external references ((3) does not hold) pointing into the same non-frozen region ((2) and (4) do not hold), and neither of them points downwards in the region stack ((5) does not hold), then they must be the same reference ((1) holds).In particular, this means that there is at most one external reference into any closed region, implying that the region graph of closed regions forms a forest.
The Topology Invariant and Fig. 1.Applying the topology invariant to all pairs of references in Fig. 1, assuming was opened after ′ , the reference → is allowed to co-exist with any other alias of since RS ⊢ ⪯ (5).The reference → is not allowed to co-exist with → since there would be two external references into the same region (1-5).However, → can co-exist with → since the former stays within its region (3).The references → and → are allowed to co-exist because i is in a frozen region (4).Finally, → is illegal both because it cannot co-exist with → , and since references in frozen regions cannot point to non-frozen regions.

Reggio is Sound
We prove soundness of our system by proving variants of progress and preservation for the respective language.(The full proofs are available in a technical report [Arvidsson et al. 2023].)Lemma 7.1.Command Language Progress A well-formed command con guration is done, has failed or can step: The command language is more permissive than the region language, since it has no way of inspecting the state of the global con guration.For example, an enter can always both fail and succeed in the command language, whereas the region language always permits exactly one of the behaviours.This a ects the formulation of progress: Lemma 7.3.Region Language Progress In a well-formed con guration where the command con guration can step, there is some e ect which steps both con gurations: Lemma 7.4.Region Language Preservation The region language preserves well-formedness for well-formed e ects: Note that Lemma 7.4 includes preservation of the topology invariant.Together, these lemmas prove the nal soundness theorem: Theorem 7.5.Soundness A program never gets stuck and it preserves well-formedness:

REGGIO IN VERONA
While Reggio regions are a stand-alone language design component, they were developed specically for the Verona programming language, from where the overarching goal (G1) stems.In this section, we describe Verona-speci c aspects and revisit concurrency-related goals (G5) and (G6).

Safe Concurrency
While regions and isolation can form the backbone of a "safe concurrency" story for a language, concurrency is an orthogonal aspect to our region design.Reggio regions can be integrated with di erent concurrency models.The necessary feature missing from this paper is a way to share regions across threads of control.
Verona uses a concurrency model based on behaviours (tasks that do not join or have a return value) that operate on cowns, short for concurrent owners.A cown is a wrapper around an iso that permits regions to be indirectly shared across multiple threads of control, but importantly does not permit direct access to its contents.Cowns and iso's are similar in that an explicit operation is needed to access their contents.In the case of iso's, access is immediate and synchronous as exclusivity is already established.In the case of cowns, access is asynchronous and will only commence after exclusive access has been established dynamically.This check requires region isolation for soundness [Cheeseman et al. 2023] and as a result, any mutable reference accessible to a thread of control is safe to access synchronously (G6).
For a complete introduction to Verona's concurrency model, see work by Cheeseman et al. [2023].

Concurrent Memory Management
As memory management must only consider objects inside the active region (G3) when determining liveness (c.f., §5), and regions are always exclusive to one thread, reference count manipulations do not need atomic instructions, tracing GC does not need barriers, and there is no need to momentarily stop all threads as in concurrent GC's [Click et al. 2005;Flood et al. 2016;Lidén and Karlsson 2018].By extension, a thread in Verona is free to mediate between program work or memory management work without informing or synchronising with other threads.Thus, we achieve concurrent memory management (G5).

Support for Di erent Memory Management Strategies
Verona currently supports three di erent memory management strategies for regions: arena allocation, reference counting, and tracing GC.How a region manages its memory is decided at use-site at creation time using a quali er on the new keyword: new iso<Arena>, new iso<RC> and new iso<GC>.As liveness is a local property, di erent regions' memory management does not interact, so we do not need to e.g., propagate this information further in the program.
Selecting memory management at use-site is desirable since it lets a programmer implement a data structure or library without having to commit to decisions that could limit its future use.Such a design also allows straightforward support for libraries that consist of multiple nested regions whose memory management can be controlled when the library is instantiated by programmatic means, e.g., through a strategy pattern or equivalent.

Memory Management of Immutable Objects
Note that the memory management o ered by regions does not extend to immutable objects, at least not conceptually.One option for implementation is going the way of Erlang and let a region have a copy of each immutable object it references.This may facilitate fast reclamation, but increases memory pressure.Furthermore, it introduces a ( ) copying overhead for transferring immutable objects across region boundaries, in sharp contrast with (G4).
In the Verona run-time, we permit immutable objects to be shared between regions.Thus, when a region is collected, we must detect its implications for liveness of immutable objects.Immutable objects must also consider roots across multiple threads.On the other hand, tracing immutable objects is easy and e cient as the structures are guaranteed not to change underfoot [Clebsch et al. 2017].How Verona manages immutable objects is out of scope of this paper.Verona is a class-based programming language.As an instance's capability is determined at use-site, methods declare an explicit self-capability, e.g., self : mut to propagate the external view of the instance into the instance.A class may provide several di erent implementations of a method overloaded on the selfcapability.

Propagating Capabilities Through Self Typing
A method can only be called on a receiver if its selfcapability matches the receiver's static type, which means that the object's treatment of itself internally will match the external view, both in terms of restrictions and abilities.For example, a method whose self-capability is paused can only be called when the receiver's region is paused.Notably, it is not permitted to call a paused method on a mut receiver because this would lead to aliasing between mut and paused (which would weaken paused from temporarily immutable to read-only).
Methods that are polymorphic in their self capability can be used to avoid multiple near-identical versions of a single method like in Listing 5.For brevity, we refrain from discussing this further.

RELATED WORK
We started out by describing related work leading up to Rust.We now extend this picture by going beyond Rust and also relating our work to garbage collection work before revisiting novelty.
Beyond Rust.In addition to what we have already covered, there is continuing research into Rust to alleviate its restrictions, including incorporating a garbage collector [Coblenz et al. 2022], careful library design [Beingessner 2015], phantom types [Yanovski et al. 2021], or proving unsafe Rust code correct [Jung et al. 2019[Jung et al. , 2017;;Noble et al. 2022].
Recent research has focused on techniques for "post-Rust languages", building on Rust's use of ownership types, but supporting more exible program topologies (and hopefully more e cient execution), typically by increasing the complexity of the type system.This remains an active research area: the tradeo s between regions, ownership, types, capabilities, e ects, topologies, restrictions etc are complex and multifaceted [Brachthäuser et al. 2022;Gordon 2020].
Pony [Clebsch et al. 2017;Franco et al. 2018] employs implicit regions, external uniqueness and ownership to o er high performance for actor programs by concurrent execution on multicore CPUs, while maintaining data-race and memory safety.Building on capabilities used to describe what programs can do with particular references [Boyland et al. 2001] Pony o ers at least six "reference capabilities": unique, thread-local, read-only, write-only, and identity-only, (globally) immutable, plus type modi ers for ephemeral (Hogg's "free") and aliased references.Reading or writing a eld depends on the capabilities of both the reference to the object, and of the eld within the object: there are 43 valid cases from 72 possible combinations of capabilities.Fernandez-Reyes et al. [2021] design Dala as a simpli ed alternative to Pony, based on three di erent kinds of objects-immutable, unique (aka isolated), and thread-local-rather than six di erent kinds of references.Dala programs are also data-race free, however this guarantee may be provided by a race detector at runtime, or by an optional/gradual type system.Milano et al. 's [2022] Gallifrey aims to be more exible than Rust, by relying at least as much on MLKit style region inference as on ownership annotations.Rather than an explicit global ownership model, Gallifrey programmers have to annotate unique (aka isolated) object elds, and identify parameters that will be consumed by a method invocation or that should be in the same region as other parameters or the method result.A dynamic "if disconnected" predicate searches the program's heap at runtime to determine of two references are mutually disjoint.
Cogent [O'Connor et al. 2021] is a derivative of Haskell for systems programming.It adopts a Rust-like discipline, permitting either multiple read-only references to objects, or a single readwrite reference.Cogent uses annotations to support both a formally de ned operational semantics, generation of executable C source code, and a proof certi cate proving that the generated code accurately implements the semantics.
Garbage Collection.Of the GC design goals, (G3) and (G5) can be met to some extent without region isolation.Here, Reggio's contribution is trading additional work to manage regions (at development time) for reduced overheads of managing memory (at run-time) due to avoiding GC techniques like remembered sets, barrier synchronisation, and stop-the-world pauses.Region isolation guarantees that a region's remembered set will always be empty.Thus, there is no cost associated with additional region partitioning due to tracking of inter-region references.
With respect to (G3), generational GC's (e.g., G1 [Detlefs et al. 2004]) and thread-local GC's (e.g., [Domani et al. 2002]) support collecting only a portion of the heap (e.g., just the young generation or one particular thread), but the shape and size of this heap is beyond programmer control (e.g., all young or thread-local objects will take part of GC, not just particular data structures).Furthermore, in the absence of (something like) region isolation, inter-region aliases must be tracked dynamically to be able to correctly compute liveness.Actor GC's that rely on actor isolation using types (e.g., Pony [Clebsch et al. 2017;Franco et al. 2018]) or copying (e.g., Erlang [Armstrong 2007]) are close as they allow individual actor-local heaps to be collected.This is similar to Reggio's regions, but Reggio supports partitioning of the heap without an imposed asynchronous indirection.
With respect to (G2), while it may be possible to run di erent GC's in di erent generations (or threads, actors, etc.), GC's typically use the same algorithm for the entire heap, with minor tweaks (e.g., to account for di erent object characteristics due to age) as do actor GC's.HRTGC is a real-time GC for mixed-criticality work-loads [Pizlo et al. 2007] that hierarchically decomposes the heap into regions that each run a di erent tracing GC, tuned di erently and with di erent collection frequency.HRTGC permits inter-region references and tracks them dynamically.In the actor world, Isolde [Yang and Wrigstad 2017] permits actors implemented using type-enforced actor isolation [Castegren and Tobias Wrigstad 2016;Castegren and Wrigstad 2017] to manage their memory concurrent with their execution, using a reference-counting based scheme.
With respect to (G5), garbage collectors like C4 [Tene et al. 2011], Shenandoah [Flood et al. 2016] and ZGC [Lidén and Karlsson 2018] provide concurrent collection with brief stop-the-world pauses to coordinate phase changes, with pause times invariant of heap sizes.Their heaps have unrestricted references, and instead rely on dynamic checks in read and write barriers.The aforementioned actor GC's [Armstrong 2007;Clebsch et al. 2017;Franco et al. 2018] support "fully concurrent" collection: an actor can choose to collect its local garbage without synchronising with any other concurrent activity.Reggio's regions additionally allow us to statically detect when an entire region is invalidated, without the need for a speci c actor collector to eventually detect the oating garbage (e.g., [Clebsch and Drossopoulou 2013]).Explicitly killing an actor to instantly free its heap is a common pattern in Erlang where it is made possible by copying objects on transfer, giving up (G4).
10 DISCUSSION First, we place Verona's design concepts into the context of all related work.Almost any ownership system paired with external uniqueness will support region isolation and dynamic recon guration.Verona's key contribution here is a negative contribution, but important nonetheless.Almost all other systems provide one or more top, global, or shared heap region, and in various ways permit references from inner/encapsulated/shorter-lived regions back to outer/enclosing/longer-lived regions.(Generational garbage collection works on a very similar principle [Jones et al. 2016]).Verona does not, and this enhanced decoupling of regions is critical to achieving many of our goals, especially about concurrency, and independent GC.
Verona's dynamic mutability and relaxed isolation is novel and di ers from other ownership and region systems.Inasmuch as region systems like the MLKit are based on inference, and are sound for all legal programs, questions of mutability and isolation don't apply-if the program is type-correct, the inference system can always place objects into regions such that no region errors will arise at runtime.Programming with more explicit regions, or with ownership and capability annotations, either lack polymorphism (e.g., Dala) or require complex resolution or viewpoint adaptation rules, or asynchronous indirections (e.g., Pony).In a way, Verona's region system trades precision for simplicity.It cannot construct data structures that consist of several morally overlapping regions, as is possible in e.g., C++ or Rust.We believe all programs written in e.g., C++ or Rust pay the price for that precision-even though most programs do not need it.
Verona's "single window of mutability" is probably its most novel concept.In pretty much every language, from FORTRAN to LISP to ML to Haskell to C++ to Pony to Gallifrey, if some code can nagle a mutable reference to an object, the program can always update the object through that reference.In Rust for example, programs can collect up "mutable borrows" (&mut) of any number of objects, pass them around as method arguments to anywhere in the program, and then mutate all the borrowed objects.Rust's "interior mutability" (aka C++'s const-cast) just increases the scope for potential mutation.This kind of indiscriminate mutation is exactly what the "single window of mutability" in Verona prevents.Once a program departs-even temporarily-from the scope of an opened region, (e.g., by opening some other closed region) the program can no longer modify Fig. 1.Region isolation of two closed regions.

Fig. 2 .
Fig. 2. Le : ′ is active and is closed.Right: we opened making it active and suspending ′ .

Fig. 3 .
Fig.3.Examples of bridge swapping.Time moves le ; we use di erent shades of blue for clarity.Subfigures 1-3 construct the cyclic linked list [ , , ], using the most recently added link as the bridge object.Subfigures 4-6 construct an iterator, for the list, and make it the bridge object.We subsequently use the iterator to iterate to the link and unlink it, before dropping the iterator and making the bridge object (and with a garbage iterator whose removal is determined by the region's memory management strategy).
new mut Cell(a) // buries a Listing 1. Code creating the region from ′ as depicted in Fig. 1.

Fig. 8 .
Fig. 8. (Non-)destructive readsField assignments are caused by the e ect swap( , ., use), which writes the value of use to .and binds the old value of .to .A use is a potentially destructive variable access ( or drop , see Fig.9).Rules region-swap-temp and region-swap-heap handle the cases where the object being assigned to is in the temporary store or on the heap.In both cases, we perform the use (which may make a variable invalid) with the helper function get (see Fig.8).We then proceed just as when loading a eld, but nish by updating the object being assigned to and update its containing store .Note that assigning and loading mutable variables are special cases of the swap and load e ects since we model mutable variables as single-eld objects.

Table 1 .
Allowed actions depending on a region's state.Incoming and outgoing denote references to mutable objects from and to other regions respectively.Bridge means only to bridge objects; any * means to any object of a previously (outgoing) or subsequently (incoming) opened region.Free object denotes ability to free individual objects inside of a region.Free region denotes the ability to free an entire region.
The relation RS ⊢ dst(ref) ⪯ src(ref) holds if dst(ref) is higher up in RS than src(ref) and ref originates from the temporary store.In other words, we allow temporary references into suspended regions from open regions.∀ref 1 , ref 2 ∈ references(⟨RS; op ; cl ; fr ⟩).