Special Delivery: Programming with Mailbox Types

The asynchronous and unidirectional communication model supported by mailboxes is a key reason for the success of actor languages like Erlang and Elixir for implementing reliable and scalable distributed systems. While many actors may send messages to some actor, only the actor may (selectively) receive from its mailbox. Although actors eliminate many of the issues stemming from shared memory concurrency, they remain vulnerable to communication errors such as protocol violations and deadlocks. Mailbox types are a novel behavioural type system for mailboxes first introduced for a process calculus by de’Liguoro and Padovani in 2018, which capture the contents of a mailbox as a commutative regular expression. Due to aliasing and nested evaluation contexts, moving from a process calculus to a programming language is challenging. This paper presents Pat, the first programming language design incorporating mailbox types, and describes an algorithmic type system. We make essential use of quasi-linear typing to tame some of the complexity introduced by aliasing. Our algorithmic type system is necessarily co-contextual, achieved through a novel use of backwards bidirectional typing, and we prove it sound and complete with respect to our declarative type system. We implement a prototype type checker, and use it to demonstrate the expressiveness of Pat on a factory automation case study and a series of examples from the Savina actor benchmark suite.


INTRODUCTION
Software is increasingly concurrent and distributed, but coordinating concurrent computations introduces a host of additional correctness issues like communication mismatches and deadlocks. Communication-centric languages such as Go, Erlang, and Elixir make it possible to avoid many of the issues stemming from shared memory concurrency by structuring applications as lightweight processes that communicate through explicit message passing. There are two main classes of communication-centric language. In channel-based languages like Go or Rust, processes communicate over channels, where a send in one process is paired with a receive in the recipient process. In actor languages like Erlang or Elixir, a message is sent to the mailbox of the recipient process, which is an incoming message queue; in certain actor languages, the recipient can choose which message from the mailbox to handle next.
Although communication-centric languages eliminate many coordination issues, some remain. For example, a process may still receive a message that it is not equipped to handle, or wait for  [Fowler et al. 2017] a message that it will never receive. Such communication errors often occur sporadically and unpredictably after deployment, making them difficult to locate and fix. Behavioural type systems [Hüttel et al. 2016] encode correct communication behaviour to support correct-by-construction concurrency. Behavioural type systems, in particular session types [Honda 1993;Honda et al. 1998;Takeuchi et al. 1994], have been extensively applied to specify communication protocols in channel-based languages [Ancona et al. 2016]. There has, however, been far less application of behavioural typing to actor languages. Existing work either imposes restrictions on the actor model to retrofit session types [Harvey et al. 2021;Mostrous and Vasconcelos 2011;Francalanza 2021, 2022] or relies on dynamic typing [Neykova and Yoshida 2017b]. We discuss these systems further in §7.
Our approach is based on mailbox types, a behavioural type system for mailboxes first introduced in the context of a process calculus [de'Liguoro and Padovani 2018]. We present the first programming language design incorporating mailbox types and we detail an algorithmic type system, an implementation, and a range of benchmarks and a factory case study. Due to aliasing and nested evaluation contexts, the move from a process calculus to a programming language is challenging. We make essential and novel use of quasi-linear typing [Ennals et al. 2004;Kobayashi 1999] to tame some of the complexity introduced by aliasing, and our algorithmic type system is necessarily co-contextual [Erdweg et al. 2015;Kuci et al. 2017], achieved through a novel use of backwards bidirectional typing [Zeilberger 2015].

Channel-based vs Actor Communication
Channel-based languages comprise anonymous processes that communicate over named channels, whereas actor-based languages comprise named processes each equipped with a mailbox. Figure 1 contrasts the approaches, and is taken from a detailed comparison [Fowler et al. 2017].
Actor languages have proven to be effective for implementing reliable and scalable distributed systems [Trinder et al. 2017]. Communication in actor languages is asynchronous and unidirectional: many actors may send messages to an actor , whereas only may receive from its mailbox. Mailboxes provide data locality as each message is stored with the process that will handle it. Since channel-based languages allow channel names to be sent, they must either sacrifice locality and reduce performance, or rely on complex distributed algorithms [Chaudhuri 2009;Hu et al. 2008].
Although it is straightforward to add a type system to channel-based languages, adding a type system to actor languages is less straightforward, as process names (process IDs or PIDs) must be parameterised by a type that supports all messages that can be received. The type is therefore less precise, requiring subtyping [He et al. 2014] or synchronisation [de Boer et al. 2007;Tasharofi et al. 2013] to avoid a total loss of modularity [Fowler et al. 2017].
The situation becomes even more pronounced when considering behavioural type systems: communication errors might be prevented by giving one end of a channel the session type A mailbox name (e.g., Future) may have different types at different points in the program. EmptyFuture types an input capability of an empty future mailbox, and denotes that the mailbox may contain a single Put message with an Int payload, and potentially many (★) Get messages each with a ClientSend payload. FullFuture types an input capability of the future after a Put message has been received, and requires that the mailbox only contains Get messages. ClientSend is an output mailbox type which requires that a Reply message must be sent; ClientRecv is an input capability for receiving the Reply. For each mailbox name, sends and receives must "balance out": if a message is sent, it must eventually be received.
de'Liguoro and Padovani [2018] introduce a small extension of the asynchronous -calculus [Amadio et al. 1998], which they call the mailbox calculus, and endow it with mailbox types. They express the Future example in the mailbox calculus as follows, where the mailbox is denoted self. emptyFuture(self) ≜ self?Put( ) . fullFuture (self, x) fullFuture(self, x) ≜ free self . done + self?Get(sender A process calculus is useful for expressing the essence of concurrent computation, but there is a large gap between a process calculus and a programming language design, the biggest being the separation of static and dynamic terms. A programming language specifies the program that a user writes, whereas a process calculus provides a snapshot of the system at a given time. A particular difference comes with name generation: in a process calculus, we can write name restrictions directly; in a programming language, we instead have a language construct (like new) which is evaluated to create a fresh name at runtime. Further complexities come with nested evaluation contexts, sequential evaluation, and aliasing. We explore these challenges in greater detail in §2.
We propose Pat 1 , a first-order programming language that supports mailbox types, in which we express the future example as follows (self is again the mailbox). The Pat program has a similar structure to the Erlang example with client, emptyFuture and fullFuture functions, and the mailbox types are similar to those in the mailbox calculus specification. There are, however, some differences compared with the Erlang future. The first is that in Pat mailboxes are first-class: we create a new mailbox with new, and receive from it using the guard expression. A guard acts on a mailbox and may contain several guards: free ↦ → frees the mailbox if there are no other references to it and evaluates ; and receive m[ − → ] from y ↦ → retrieves a Session typing was originally studied in the context of process calculi (e.g., [Honda et al. 1998;Vasconcelos 2012]), but later work ; Gay and Vasconcelos 2010; Wadler 2014] introduced session types for languages based on the linear -calculus. The more relaxed view of linearity in the mailbox calculus makes language integration far more challenging. A mailbox name may be used several times to send messages, but only once to receive a message. The intuition is that while sends simply add messages to a mailbox, it is a receive that determines the future behaviour of the actor. To illustrate, Fig. 2 shows a fragment of the future example from §1 with two sends to the future mailbox (lines 5 and 6), and a single receive (line 8). In the mailbox calculus, a name remains constant and cannot be aliased; this is at odds with idiomatic programming where expressions are aliased with let bindings or function application. Moreover functional languages provide nested evaluation contexts and sequential evaluation.  Ensuring appropriate mailbox use is challenging in the presence of aliasing: for example, we can write a function that attempts to use a mailbox after it has been freed (Fig. 3a). Unlike in our situation, use-after-free errors are not an issue with a fully linear type system, since we cannot use a resource after it has been consumed.
We could require that a name cannot be used after it has been guarded upon by insisting that the subject and body of a guard expression are typable under disjoint type environments. Indeed, such an approach correctly rules out the error in Fig. 3a, but the check can easily be circumvented. Fig. 3b aliases the output capability for the mailbox, and the new name prevents the typechecker from realising that it has been used in the body of the guard. Similarly, Fig. 3c uses nested evaluation contexts, meaning that the next use of a mailbox variable is not necessarily contained within a subexpression of the guard.
Much of the intricacy arises from using a mailbox name many times as an output capability. In each process, we can avoid the problems above using three principles: (1) No two distinct variables should represent the same underlying mailbox name.
(2) Once let-bound to a different name, a mailbox variable is considered out-of scope.
(3) A mailbox name cannot be used after it has been used in a guard expression. These principles ensure syntactic hygiene: the first and second handle the disconnect between static names and their dynamic counterparts, allowing us to reason that two syntactically distinct variables indeed refer to different mailboxes. The third ensures that a mailbox name is correctly 'consumed' by a guard expression, allowing us to correctly update its type.
Aliasing through communication. Consider the following example, where mailbox receives the message m[ ], where is already free in the continuation of the receive clause: Here, although the code suggests that and are distinct, aliasing is introduced through communication (violating principle 1). The mailbox calculus uses a dependency graph (DG) both to avoid issues with aliasing and to eliminate cyclic dependencies and hence deadlocks. As an example, the mailbox calculus process ( ) ( ) ( ! m[ ] ∥ free b ∥ a?m[ ] . free a) would have DG ( ) ( ) ({ , }) due to the dependency arising from sending over . Alas, a language implementation cannot use this approach as it relies on knowing runtime names directly. To see why, consider the Pat program on the left, which evaluates to an analogous configuration. The first issue is how to create a scoped DG from new: one option is to introduce a scoped construct let mailbox in , but this approach fails as soon as we rename using a let-binder. A more robust approach is to follow Ahmed et al. [2007] and Padovani [2019] and endow mailbox types with a type-level identity by giving new an existential type and introducing a scoped unpack construct. However there still remain two issues: first, it is unclear how to extend DGs to capture the more complex scoping and sequencing induced by nested contexts. Second, each mailbox type would require an identity (e.g. ! Msg) which becomes too restrictive, since we would need to include identities in message payload types when communicating names. As an example, each client of the Future example from §1 would require a separate message type.

The Pat Solution: Quasi-linear Typing
The many-sender, single-receiver pattern is closely linked to quasi-linear typing [Kobayashi 1999]; our formulation is closer to that of Ennals et al. [2004]. Quasi-linear types were originally designed to overcome some limitations of full linear types in the context of memory management and programming convenience and allow a value to be used once as a first-class (returnable) value, but several times as a second-class value [Osvald et al. 2016]. A second-class value can be consumed within an expression, for example as the subject of a send operation, but cannot escape the scope in which it is defined.
This distinction maps directly onto the many-writer, single-reader communication model used by the mailbox calculus. We augment mailbox types with a usage: either •, a returnable reference that allows a type to appear in the return type of an expression; or •, a 'second-class' reference. The subject of a guard must be returnable. With usage information we can ensure that: (1) there is only one returnable reference for each mailbox name in a process (2) only returnable references can be renamed, avoiding problems with aliasing (3) the returnable reference is the final lexical use of a mailbox name in a process Quasi-linear types rule out all three of the previous examples. In useAfterFree, is consumed by the guard expression and cannot be used thereafter. In useAfterFree2, since is the subject of a let binding, it must be returnable and therefore cannot be used in the body of the binding. In useAfterFree3, since is used as the subject of a guard expression, that use must be first-class and therefore the last lexical occurrence of , ruling out the use of in the outer evaluation context. Quasi-linear typing cannot account for inter-process deadlocks, but can still rule out self-deadlocks.
Ruling out aliasing through communication. Quasi-linear types alone do not safeguard against introducing aliasing through communication, and we cannot use DGs for the reasons stated above. However, treating all received names as second-class, coupled with some simple syntactic restrictions (e.g. by ensuring that either all message payloads or all variables free in the body of the receive clause have base types) eliminates unsafe aliasing.
Summary. Quasi-linear types and the lightweight syntactic checks outlined above ensure that mailboxes are used safely in a concurrent language that allows aliasing, and obviate the need for the static global dependency graph used in the mailbox calculus. We show that the checks are not excessively restrictive by expressing all of the examples shown by de'Liguoro and Padovani [2018], and 10 of the 11 Savina benchmarks [Imam and Sarkar 2014] used by Neykova and Yoshida [2017b] to demonstrate expressiveness of behavioural type systems for actor languages ( §6.2).

PAT: A CORE LANGUAGE WITH MAILBOX TYPES
This section introduces Pat, a core first-order programming language with mailbox types, along with a declarative type system and an operational semantics. Figure 4 shows the syntax for Pat. We defer discussion of types to §3.2.

Syntax
Programs and Definitions. A program (S, − → , ) consists of a signature S which maps message tags to payload types; a set of definitions ; and an initial term . Each definition def ( − −− → : ): { } is a function with name , annotated arguments − −− → : , return type , and body . We write P ( ) to retrieve the definition for function , and P (m) to retrieve the payload types for message m.
Values. It is convenient for typing to introduce a syntactic distinction between values and computations, inspired by fine-grain call-by-value [Levy et al. 2003]. Values , include variables and constants ; we assume that the set of constants includes at least the unit value () of type 1.
Terms. The functional fragment of the language is largely standard. Every value is a term. The only evaluation context is let : = in , which evaluates term of type , binding its result to in continuation . The type annotation is a technical convenience and is not necessary in our implementation ( §3). Function application ( − → ) applies function to arguments − → . As usual, we use ; as sugar for let : 1 = in , where does not occur in .
In the concurrent fragment of the language, spawn spawns term as a separate process, and new creates a fresh mailbox name. . The fail guard is triggered when an unexpected message has arrived; free ↦ → is triggered when a mailbox is empty and there are no more references to it in the system; and receive m[ − → ] from ↦ → is triggered when the mailbox contains a message with tag m, binding its payloads to − → and continuation mailbox with updated mailbox type to in continuation term . We write free as syntactic sugar for guard : 1 {free ↦ → ()}, and fail as syntactic sugar for guard : 0 {fail}. We require that each clause within a guard expression is unique.

Type system
This section describes a declarative type system for Pat. We begin by discussing mailbox types in more depth, in particular showing how to define subtyping and equivalence.
3.2.1 Types. A mailbox type consists of a capability, either output ! or input ?, and a pattern. A system can contain multiple references to a mailbox as an output capability, but only one as an input capability. A pattern is a commutative regular expression, i.e., a regular expression where composition is unordered. The 1 pattern is the unit of pattern composition ⊙, denoting the empty mailbox. The 0 pattern denotes the unreliable mailbox, which has received an unexpected message. It is not possible to send to, or receive from, an unreliable mailbox, but we will show that reduction does not cause a mailbox to become unreliable. The pattern m denotes a mailbox containing a single message m 2 . Pattern choice ⊕ denotes that the mailbox contains either messages conforming to pattern or . Pattern composition ⊙ denotes that the mailbox contains messages pertaining to and (in either order). Finally, ★ denotes replication of , so ★m denotes that the mailbox can contain zero or more instances of message m. Mailbox patterns obey the usual laws of commutative regular expressions: 1 is the unit for ⊙, while 0 is the unit for ⊕ and is cancelling for ⊙. Composition ⊙ is associative, commutative, and distributes over ⊕; and ⊕ is associative and commutative.
Pattern semantics. It follows that different syntactic representations of patterns may have the same meaning, e.g. patterns 1 ⊕ 0 ⊕ (m ⊙ n) and 1 ⊕ (n ⊙ m). Following [de'Liguoro and Padovani 2018], we define a set-of-multisets semantics for mailbox patterns; the intuition is that each multiset defines a configuration of messages that could be present in the mailbox. For example the semantic representation of both of the patterns above is {⟨⟩, ⟨m, n⟩}. We let A, B range over multisets.
The pattern 0 is interpreted as an empty set; 1 as the empty multiset; ⊕ as set union; ⊙ as pointwise multiset union; m as the singleton multiset; and ★ as the infinite set containing any number of concatenations of interpretations of .
Usage annotations. A type can be a base type , or a mailbox type . As discussed in §2, quasilinearity is used to avoid aliasing issues. Usage-annotated types , annotate mailbox types with a usage: either second class (•), or returnable (•). There are no restrictions on the use of a base type. Only values with a returnable type can be returned from an evaluation frame.

Operations on types.
We say that a type is returnable, written returnable( ), if is a base type or a returnable mailbox type • . The ⌊−⌋ operator ensures that a type is returnable, while the ⌈−⌉ operator ensures that a mailbox type is second-class: We also extend the operators to usage-annotated types (e.g. ⌈ • ⌉ = • ) and type environments.
Subtyping. With a semantics defined, we can consider subtyping. A pattern is included in a pattern , written ⊑ , if every multiset in the semantics of also occurs in the semantics of pattern , i.e., ⊑ ≜ ⊆ .
Definition 3.1 (Subtyping). The subtyping relation is defined by the following rules: 2 Unlike in §1, our formalism does not pair a message tag with its payload; instead, tags are associated with payload types via the program signature. This design choice allows us to more easily compare the declarative system with the algorithmic system in §4, and unlike [de'Liguoro and Padovani 2018] means we need not define types and subtyping coinductively.
, , Simon Fowler, Duncan Paul Attard, Franciszek Sowul, Simon J. Gay, and Phil Trinder Usage subtyping is defined as the smallest reflexive operator defined by axioms ≤ and • ≤ •. We write ≃ if both ≤ and ≤ , i.e. either , are the same base type, or are mailbox types with the same capability and pattern semantics.
Base types are subtypes of themselves. As with previous accounts of subtyping in actor languages [He et al. 2014], subtyping is covariant for mailbox types with a receive capability: a mailbox can safely be replaced with another that can receive more messages. Likewise subtyping is contravariant for mailboxes with a send capability: a mailbox can safely be replaced with another that can send a smaller set of messages. Intuitively, as returnable usages are more powerful than second-class usages, returnable types can be used when only a second-class type is required.
Following de'Liguoro and Padovani [2018] we introduce names for particular classes of mailbox types. Intuitively, relevant mailbox names must be used, whereas irrelevant names need not be. Likewise reliable and usable names can be used, whereas unreliable and unusable names cannot.
Definition 3.2 (Relevant, Reliable, Usable). A mailbox type is relevant if ̸ ≤ !1, and irrelevant otherwise; reliable if ̸ ≤ ?0 and unreliable otherwise; and usable if ̸ ≤ !0 and unusable otherwise. Definition 3.3 (Unrestricted and Linear Types). We say that a type is unrestricted, written un( ), if = , or = !1 • . Otherwise, we say that is linear.
Our type system ensures that variables with a linear type must be used, whereas variables with an unrestricted type can be discarded. We extend subtyping to type environments, making it possible to combine type environments, as in [Crafa and Padovani 2017;de'Liguoro and Padovani 2018].
Definition 3.4 (Environment subtyping). Environment subtyping Γ 1 ≤ Γ 2 is defined as follows: We include a notion of weakening into the subtyping relation, so an environment Γ can be a subtype environment of Γ ′ if it contains additional entries of unrestricted type.
Type combination. Mailbox types ensure that sends and receives "balance out", meaning that every send is matched with a receive. For example, using a mailbox at type !Put and ?(Put ⊙ ★Get) results in a mailbox type ?(★Get). The key technical device used to achieve this goal is type combination: combining a mailbox type ! and a mailbox type ! results in an output mailbox type which must send both and ; combining an input and an output capability results in an input capability that no longer needs to receive the output pattern. We can also combine identical base types. Note that it is not possible to combine two input capabilities as this would permit simultaneous reads of the same mailbox.
Definition 3.5 (Type combination). Type combination ⊞ is the commutative partial binary operator defined by the following axioms: Following Crafa and Padovani [2017], it is convenient to identify types up to commutativity and associativity, e.g. we do not distinguish between ?(A ⊙ B) • and ?(B ⊙ A) • . We may however need to use subtyping to rewrite a type into a form that allows two mailbox types to be combined (e.g. to combine !A and ?(★A), we would need to use subtyping to rewrite the latter type to ?(A ⊙ ★A)).

, ,
The following usage combination is not commutative as a • variable use must occur before a • use (ensuring that the returnable use is the variable's last lexical occurrence). Furthermore, note that • ⊲ • is undefined (ensuring that there is only one returnable instance of a variable per thread).
We can now define usage-annotated type and environment combination.
Definition 3.8 (Environment combination (Γ)). Usage-annotated environment combination Γ 1 ⊲ Γ 2 is the smallest partial operator on type environments closed under the following rules: We use usage-annotated type combination when combining the types of two variables used in subsequent evaluation frames (i.e. in the subject and body of a let expression). We also require disjoint combination, where two environments are only able to share variables of base type: Definition 3.9 (Disjoint environment combination). Disjoint environment combination Γ 1 + Γ 2 is the smallest partial operator on type environments closed under the following rules: 3.2.3 Typing rules. Fig. 5 shows the declarative typing rules for Pat. As the system is declarative it helps to read the rules top-down. Terms. Term typing has the judgement Γ ⊢ P : , which states that when defined in the context of program P, under environment Γ, term has type . We omit the P parameter in the rules for readability. Rule T-Var types a variable in a singleton environment; we account for weakening in T-Sub. Rule T-Const types a constant under an empty environment; we assume an implicit schema mapping constants to types, and assume at least the unit value () of type 1. Rule T-App types function application according to the definition in P. Each argument must be typable under a disjoint type environment to avoid aliasing mailbox names in the body of the function.
Rule T-Let types sequential composition. The subject of the let expression must be returnable; since Γ 1 ⊲ Γ 2 is defined, we know that if the subject (typable using Γ 1 ) contains a returnable variable, then it cannot appear in Γ 2 . This avoids aliasing and use-after-free errors.
Rule T-Spawn types spawning a term of unit type as a new process. The type environment used to type can contain any number of returnable, but the conclusion of the rule 'masks' any returnable types as second-class. Intuitively, this is because there is no need to impose an ordering on how a variable is used in a separate process: while within a single process a guard on some name should not precede a send on , there is no such restriction if the two expressions are executing in concurrent processes. Rule T-New creates a fresh mailbox with type ?1 • , since subsequent sends and receives must "balance out" to an empty mailbox.
Rule T-Send types a send expression ! m[ where a message m with payloads − → is sent to a mailbox . Value must be a reference with type !m • , meaning that it can be used to send message T-App Typing rules for guards Γ ⊢ P − → : :: Γ ⊢ P : :: TG-GuardSeq

TG-Recv
Pat declarative term typing m. The mailbox only needs to be second-class, but subtyping means that we can also send to a first-class name. All payloads − → must be subtypes of the types defined by the signature for message m, and payloads must be typable under separate environments to avoid aliasing when receiving a message. Unlike in session-typed functional programming languages, sending is a side-effecting operation of type 1, and the behavioural typing is accounted for in environment composition.
Rule T-Guard types the expression guard : { − → }, that retrieves from mailbox with some pattern using guards − → . The first premise ensures that under a type environment Γ 1 , mailbox has type ? • : the mailbox should have a receive capability with pattern , and must be returnable. Demanding that the mailbox is returnable rules out use-after-free errors since we cannot use the mailbox name in the continuation. The second premise states that under type environment Γ 2 , guards − → all return a value of type and correspond to pattern . The third premise requires that the pattern assertion is contained within . The final premise, ⊨ , ensures that is in pattern normal form: the pattern should be a disjunction of pattern literals. That is 0, 1, or m ⊙ , where is equivalent to without message m.
Finally, rule T-Sub allows the use of subtyping. Subtyping on type environments is crucial when constructing derivations, e.g. two patterns may have the same semantics but differ syntactically.
Applying T-Sub makes it possible to rewrite mailbox types so that they can be combined by the type combination operators. We also allow the usual use of subsumption on return types, e.g. allowing a value with a subtype of a function argument to be used.
Guards. Rule TG-GuardSeq types a sequence of guards, ensuring that each guard is typable under the same type environment and with the same return type. Rule TG-Fail types a failure guard: since the type system will ensure that such a guard is never evaluated, it can have any type environment and any type, and is typable under pattern literal 0. Rule TG-Free types a guard of the form free ↦ → , where has type . Finally, rule TG-Recv types a guard of the form receive m[ − → ] from ↦ → , that retrieves a message with tag m from the mailbox, binding its payloads (whose types are retrieved from the signature for message m) to − → , and re-binding the mailbox to with an updated type in continuation . The payloads are made usable rather than returnable, as otherwise the payloads could interfere with the names in the enclosing context.
Pattern residual. The pattern residual / m calculates the pattern after m is consumed, and corresponds to the Brzozowski derivative [Brzozowski 1964] over a commutative regular expression. The residual of 0, 1, or n (where n ≠ m) with respect to a message tag m is the unreliable type 0. The derivative of m with respect to m is 1. The derivative operator distributes over ⊕, and the derivative of concatenation is the disjunction of the derivative of each subpattern.
Example. We end this section by showing the derivation for the client definition from the future example in §1, which creates a future and self mailbox, initialises the future with a number, and then requests and prints the result. In the following, we abbreviate future to f, self to , and result to . We assume that the program includes a signature S = [Put ↦ → Int, Get ↦ → !Reply, Reply ↦ → Int], and the emptyFuture and fullFuture definitions from §1. We split the derivation into three subderivations. Since it is easier to read derivations top-down, we start by typing the guard expression. In the following, we refer to the receive guard as , and name the first derivation D 1 : The type of the s mailbox in the subject of the guard expression is ?(Reply ⊙ 1) • denoting that the mailbox can contain a Reply message and will then be empty. The receive guard binds at type ?1 • and at Int, freeing and using in the print expression. The Reply annotation on the guard is a subpattern of the pattern of . The above derivation is used within derivation D 2 : Runtime type environments Δ ::= · | Δ, :

Fig. 6. Pat operational semantics
Here f is used to send a Put and then a Get with s of type !Reply • as payload. As the two sends to the f message are sequentially composed, the type of f at the root of the subderivation is !(Put ⊙ Get) • . Since s is used at type ?(Reply ⊙ 1) • in D 1 , the send and receive patterns balance out to the empty mailbox type ?1 • . Finally, we can construct the derivation for the entire term: Since we let-bind f to new, f must have type ?1 • . Definition emptyFuture requires an argument of type ?(Put ⊙ ★Get) • ; since the function application appears in the body of the spawn we can mask the usage annotation to •, and use environment subtyping to rewrite the type of f to ?( (Put ⊙ Get) ⊙ 1) • . This then balances out with the use of f in D 2 , completing the derivation. Figure 6 shows the runtime syntax and reduction rules for Pat. We extend values with runtime names . The concurrent semantics of the language is described as a nondeterministic reduction relation on a language of configurations, which resemble terms in the -calculus. Configuration , Σ is a thread evaluating term , with frame stack Σ; we will discuss frame stacks in the next

Operational Semantics
in mailbox ; name restriction ( )C binds name in C; and C ∥ D denotes the parallel composition of C and D.
Frame stacks. We use frame stacks [Ennals et al. 2004;Pitts 1998] rather than evaluation contexts for technical convenience. A frame ⟨ , ⟩ is a pair of a variable and a continuation , where is free in . A frame stack is an ordered sequence of frames, where denotes the empty stack.
Initial configuration. The semantics envisages a single static term (i.e., program text) to be evaluated in the context of an empty frame stack: , .
Reduction rules. Frame stacks are best demonstrated by the E-Let and E-Return rules: intuitively, let : = in evaluates , binding the result to in . The rule adds a fresh frame ⟨ , ⟩ to the top of a frame stack, and evaluates . Conversely, E-Return returns into the parent frame: if the top frame is ⟨ , ⟩, then we can evaluate the continuation with substituted for . Rule E-App evaluates the body of function with arguments − → substituted for the parameters − → .
Rule E-New creates a fresh mailbox name restriction and returns it into the calling context. Rule E-Send sends a message with tag m and payloads − → to a mailbox , returning () to the calling context and creating a sent message configuration ← m[

− →
]. Rule E-Spawn spawns a computation as a fresh process, with an empty frame stack. Rule E-Free allows a name to be garbage collected if it is not contained in any other thread, evaluating the continuation of the free guard. Finally, rule E-Recv handles receiving a message from a mailbox, binding the payload values to − → and updated mailbox name to in continuation . The remaining rules are administrative.

Runtime typing.
To prove metatheoretical properties about Pat we introduce a type system on configurations; this type system is used only for reasoning and is not required for typechecking.
Runtime type environments. The runtime typing rules make use of a type environment Δ that maps variables to types that do not contain usage information. Usage information is inherently only useful in constraining sequential uses of a mailbox variable, where guards are blocking, whereas it makes little sense to constrain concurrent usages of a variable. Runtime type environment combination on Δ 1 ⊲⊳ Δ 2 is similar to usage-annotated type environment combination but with two differences: it is commutative to account for the unordered nature of parallel threads, and type combination does not include usage information.
Definition 3.10 (Environment combination (Δ)). Environment combination Δ 1 ⊲⊳ Δ 2 is the smallest partial commutative binary operator on type environments closed under the following rules: Disjoint combination on runtime type environments Δ 1 + Δ 2 (omitted) is defined analogously to disjoint combination on Γ.
Runtime typing rules. Figure 7 shows the runtime typing rules. Rule TC-Nu types a name restriction if the name is of type ?1; in turn this ensures that sends and receives on the mailbox "balance out" across threads. Rule TC-Par allows configurations C and D to be composed in parallel if they are typable under combinable runtime type environments. Rule TC-Message types a message configuration ← m[

− →
]. Name of type !m cannot appear in any of the values sent as a payload. Each payload value must be a subtype of the type defined by the message signature, under the

Configuration Typing
Δ ⊢ C second-class lifting of a disjoint runtime type environment. Rule TC-Subs allows subtyping on runtime type environments; the subtyping relation Δ ≤ Δ ′ is analogous to subtyping on Γ.

TC-Nu
Thread and frame stack typing. Rule TC-Thread types a thread, which is a pair of a currentlyevaluating term, typable under an environment Γ 1 , and a stack frame, typable under an environment Γ 2 . The combination Γ 1 ⊲ Γ 2 should result in the returnable lifting of Δ: intuitively, we should be able to use every mailbox variable in Δ as returnable in the thread. TC-Thread makes use of the frame stack typing judgement Γ ⊢ ▶ Σ (inspired by [Ennals et al. 2004]), which can be read "under type environment Γ, given a value of type , frame stack Σ is well-typed". The empty frame stack is typable under the empty environment given any type. A non-empty frame stack ⟨ , ⟩ · Σ is well-typed if has some returnable type , given a variable of type . The remainder of the stack must then be well-typed given . We combine the environments used for typing the head term and the remainder of the stack using ⊲ as we wish to account for sequential uses of a mailbox; for example, in the term ! m[ ]; ! n[ ], would have type !(m ⊙ n) • .

Properties.
We can now state some metatheoretical results. We relegate proofs to Appendix D.
Typability is preserved by reduction; the proof is nontrivial since we must do extensive reasoning about environment combination.
Preservation implies mailbox conformance: the property that a configuration will never evaluate to a singleton failure guard. To state mailbox conformance, it is useful to define the notion of a configuration context H ::= ( )H | H ∥ C | [ ], Σ , that allows us to focus on a single thread.
Progress. To prove a progress result for Pat, we begin with some auxiliary definitions.
Next, we classify canonical forms, which give us a global view of a configuration. Every well typed process is structurally congruent to a canonical form.
Definition 3.14 (Canonical form). A configuration C is in canonical form if it is of the form: Definition 3.15 (Waiting). We say that a term is waiting on mailbox for a message with tag m, written waiting( , , m), if can be written guard : Let fv(−) denote the set of free variables in a term or frame stack Σ. We can then use canonical forms to characterise a progress result: either each thread can reduce, has reduced to a value, or is waiting for a message which has not yet been sent by a different thread.
Theorem 3.16 (Partial Progress). Suppose ⊢ P and · ⊢ P C where C is in canonical form: Then for each , either: where − → are the guard clauses of .
A key consequence of Theorem 3.16 is the absence of self-deadlocks: since we can only guard on a returnable mailbox, and a returnable name must be the last occurrence in the thread, it cannot be that the guard expression is blocking a send to the same mailbox in the same thread. As we cannot use dependency graphs ( §2.2), our progress result does not rule out inter-process deadlocks.

ALGORITHMIC TYPING
Writing a typechecker based on Pat's declarative typing rules is challenging due to nondeterministic context splits, environment subtyping, and pattern inclusion. MC 2 [Padovani 2018b] is a typechecker for the mailbox calculus, based on a typechecker for concurrent object usage protocols [Padovani 2018c]. The MC 2 type system has, however, not been formalised. We adopt several ideas from MC 2 , especially algorithmic type combination, and adapt the approach for a programming language.
Type system overview. Our algorithmic type system takes a co-contextual [Erdweg et al. 2015] approach: rather than taking a type environment as an input to the type-checking algorithm, we produce a type environment as an output. The intuition is that (read bottom-up), splitting an environment into two sub-environments is more difficult than merging two environments inferred from subexpressions. We also generate inclusion constraints on patterns to be solved later.
Bidirectional type systems [Dunfield and Krishnaswami 2022; Pierce and Turner 2000] split typing rules into two classes: those that synthesise a type for a term (Γ ⊢ ⇒ ), and those that check that a term has type (Γ ⊢ ⇐ ). Bidirectional type systems are syntax-directed and amenable to implementation.
We use a co-contextual variant of bidirectional typing first introduced by Zeilberger [2015]. The main twist is the variable rule, which becomes a checking rule and records the given variable-type mapping in the inferred environment.

Algorithmic Type System
Extended syntax and annotation. A key difference in comparison to the declarative type system is the addition of pattern variables , that act as a placeholder for part of a pattern and are generated , , Simon Fowler, Duncan Paul Attard, Franciszek Sowul, Simon J. Gay, and Phil Trinder Unrestrictedness . Pat algorithmic type operations during typechecking. We can then generate and solve inclusion constraints on patterns. Figure 8 shows the extended syntax used in the algorithmic system.
Constraints. An important challenge for the algorithmic type system is determining whether one pattern is included within another: e.g. m ⊑ ★m. Given that patterns may contain pattern variables, we may need to defer inclusion checking until more pattern variables are known, so we introduce inclusion constraints <: which require that pattern is included in pattern .
4.1.1 Algorithmic type operations. Fig. 9 shows the algorithmic type combination operators.
Unrestrictedness and subtyping. The algorithmic unrestrictedness operation unr( ) ▶ Φ states that is unrestricted subject to constraints Φ, and the definition reflects the fact that a type is unrestricted in the declarative system if it is a base type or a subtype of !1 • . Algorithmic subtyping is similar: a base type is a subtype of itself, and we check that two mailbox types with the same capability are subtypes of each other by generating a contravariant constraint for a send type, and a covariant constraint for a receive type.
Algorithmic type join. Declarative mailbox typing relies on the subtyping rule to manipulate types into a form where they can be combined with the type combination operators, e.g., ! ⊞ ?( ⊙ ) = ? . The algorithmic type system cannot apply the same technique as it does not know, a priori, the form of each pattern. Instead, the algorithmic type join operation allows the combination of two mailbox types irrespective of their syntactic form. Combining two send types is the same as in the declarative system, but combining a send type with a receive type (and vice versa) is more interesting: say we wish to combine ! and ? . In this case, we generate a fresh pattern variable ; the result is ? along with the constraint that ( ⊙ ) <: : namely, that the send pattern concatenated with the fresh pattern variable is included in the pattern .
As an example, joining !m and ?(n ⊙ m) produces a receive mailbox type ? and a constraint (m ⊙ ) <:(n ⊙ m), for which a valid solution is ↦ → n, and hence the expected combined type ?n.
Algorithmic type merge. In the declarative type system branching control flow requires that each branch is typable under the same type environment (using the T-Subs rule). The algorithmic type system instead generates constraints that ensure that each type is used consistently across branches using the algorithmic type merge operation 1 ⊓ 2 ▶ ; Φ. Two base types are merged if they are identical. In the case of mailbox types, the function takes the maximum usage annotation, so max(•, •) = •. It ensures that when merging two output capabilities the patterns are combined using pattern disjunction. Conversely merging two input capabilities generates a new pattern variable that must be included in both merged patterns.
Algorithmic environment combination. We can extend the algorithmic type operations to type environments; the (omitted, see Appendix A) rules are adaptations of the corresponding declarative operations. Notably, when combining two environments used in branching control flow such as conditionals where an output mailbox ! is used in one environment but not the other, the resulting type is !( ⊕ 1) to signify the choice of not sending on the mailbox name.
Nullable type environments. Checking a fail guard produces a null environment ⊤ which can be composed with any other type environment, as shown by the following definition: Definition 4.1 (Nullable environment combination). For each combination operator ★ ∈ { , ⊓, +} we extend environment combination to nullable type environments, Ψ 1 ★ Ψ 2 ▶ Ψ; Φ by extending each environment combination operation with the following rules: Nullable type environments are a supertype of every defined type environment: Θ ≤ ⊤. Figure 10 shows the Pat algorithmic typing of programs, definitions and terms. The key idea is to remain in checking mode for as long as possible, in order to propagate type information to the variable rule and construct a type environment. We write Synthesis. Our synthesis judgement has the form ⇒ P ▶ Θ; Φ, which can be read "synthesise type for term under program P, producing type environment Θ and constraints Φ". Here, and P are inputs of the judgement, whereas , Θ, and Φ are outputs. The checking judgement ⇐ P ▶ Θ; Φ can be read "check that term has type under program P, producing type environment Θ and constraints Φ". Here, , P, and are inputs of the judgement, whereas Θ and Φ are outputs. As in the declarative system we omit the P annotation in the rules for readability.
Rule TS-Const assigns a known base type to a constant, and rule TS-New synthesises a type ?1 • (analogous to T-New); both rules produce an empty environment and constraint set.
Rule TS-Spawn checks that the given computation has the unit type, synthesises type 1, and infers a type environment Θ and constraint set Φ. Like T-Spawn in the declarative system, the usability annotations are masked as usable since usability restrictions are process-local.
Message sending ! m[

− →
] is a side-effecting operation, and so we synthesise type 1. Rule TS-Send first looks up the payload types − → in the signature, and checks that message target has mailbox type !m • . In performing this check, the type system will produce environment Θ ′ that contains an entry mapping the variable in to the desired mailbox type !m • . Next, the algorithm checks each payload value against the payload type described by the signature. The resulting environment is the algorithmic disjoint combination of the environments produced by checking each payload, and the resulting constraint set is the union of all generated constraints. Function application is similar: rule TS-App looks up the type signature for function and checks that all arguments have the expected types. The resulting environment is again the disjoint combination of the environments, and the constraint set is the union of all generated constraints. Checking. Rule TC-Var checks that a variable has type , producing a type environment : . The TC-Let rule checks that a let-binding let : = in has type : first, we check that has type ⌊ ⌋ noting that only values of returnable type may be returned, producing environment Θ 1 and constraints Φ 1 . Next we check that the body has type , producing environment Θ 2 and Φ 2 . The next step is to check whether the types of the variable inferred in Θ 2 corresponds with the annotation. The check meta-function ensures that if is not contained within Θ 2 , then the type of is unrestricted; and conversely if is contained within Θ 2 , then the annotation is a subtype of the inferred type as the annotation is a lower bound on what the body can expect of .

Constraint generation for programs and definitions
Rule TC-Guard checks that a guard expression guard : { − → } has return type . First, the rule checks that the guard sequence − → has type , producing nullable environment Ψ, constraint set Φ 1 , and pattern in pattern normal form. Next, the rule checks that the mailbox name has type ? • , producing environment Θ ′ and constraint set Φ 2 . Finally, the rule calculates the disjoint combination of Ψ and Θ ′ , producing final environment Θ and constraints Φ 3 .
Finally, rule TC-Sub states that if a term is synthesisable with type , where is a subtype of , then is checkable with type . The resulting environment is that produced by synthesising the type for , and the resulting constraint set is the union of the synthesis and subtyping constraints. , ,

Fig. 11. Pat algorithmic typing (guards)
Un-annotated let expressions. Although our core calculus assumes an annotation on let expressions, this is unnecessary if the let-bound variable is used in the continuation , or has a synthesisable type. Specifically, TC-LetNoAnn1 allows us to check the type of the continuation and inspect the produced environment for the type of , which can be used to check . Similarly, TC-LetNoAnn2 allows us to type a let-binding where is not used in the continuation, as long as the type of is synthesisable and unrestricted.
We use the explicitly-typed representation in the core calculus for simplicity and uniformity, however the implementation follows the above approach to avoid needless annotations.
Guards. Figure 11 shows the typing rules for guards; the judgement { } ⇐ ▶ Ψ; Φ; can be read "Check that guard has type , producing environment Ψ, constraints Φ, and closed pattern literal in pattern normal form with respect to ". Rule TCG-Guards types a guard sequence, producing the algorithmic merge of all environments and the sum of all produced patterns. Rule TCG-Fail types the fail guard with any type and produces a null type environment, empty constraint set, and pattern 0. Rule TCG-Free checks that guard free ↦ → has type by checking that has type ; the guard produces pattern 1.
Finally, rule TCG-Recv checks that a receive guard receive m[ − → ] from ↦ → has type . First, the rule checks that has type , producing environment Θ ′ , : ? • and constraint set Φ 1 ; since a mailbox type with input capability is linear, it must be present in the inferred environment. Next, the rule checks that the inferred types for − → in Θ ′ are compatible with the payloads for m declared in the signature, producing constraint set Φ 2 . As with the declarative rule, to rule out unsafe aliasing either the payloads or inferred environment must consist only of base types. The resulting environment is Θ (i.e., the inferred environment without the mailbox variable or any payloads). The resulting constraint set is the union of Φ 1 and Φ 2 along with an additional constraint which ensures that / m is included in , allowing us to produce the closed PNF literal m ⊙ ( / m).

Metatheory
We can now establish that the algorithmic type system is sound and complete with respect to the declarative type system. We begin by introducing the notion of pattern substitutions and solutions.
A pattern substitution Ξ is a mapping from type variables to (fully-defined) patterns ; applying Ξ to a pattern substitutes all occurrences of a type variable for Ξ( ). We extend application of pattern substitutions to types and environments. We write pv( ) for the set of pattern variables in a pattern and extend it to types and environments.
Definition 4.2 (Pattern solution). A pattern substitution Ξ is a pattern solution for a constraint set Φ (or solves Φ) if pv(Φ) ⊆ dom(Ξ) and for each <: ∈ Ξ, we have that Ξ( ) ⊑ Ξ( ). A solution Ξ is a usable solution if its range does not contain any pattern equivalent to 0. 4.2.1 Algorithmic soundness.
Definition 4.3 (Covering solution). We say that a pattern substitution Ξ is a covering solution for a derivation If a term is well typed in the algorithmic system then, given a covering solution, the term is also well typed in the declarative system.

Algorithmic completeness.
We also obtain a completeness result, but only for the checking direction. This is because the type system requires type information to construct a type environment.
In practice the lack of a completeness result for synthesis is unproblematic since all functions have return type annotations, and therefore the only terms typable in the declarative system but unsynthesisable are top-level terms containing free variables. In the following we assume that program P is closed, i.e. no definitions or message payloads contain type variables.
An unannotated let binding let = in is also typable by the algorithmic type system if either occurs free in , or the type of is synthesisable; in practice this encompasses both base types and linear usages of mailbox types, i.e. the vast majority of use cases.

Constraint solving
Constraint solving is covered in depth by Padovani [2018c], so we provide an informal overview: Identify and group bounds A pattern bound is of the form <: i.e. a constraint whose righthand-side is a pattern variable. We firstly group all pattern bounds using pattern disjunction, e.g. a constraint set { <: , <: ,

EXTENSIONS
It is straightforward to extend Pat with product and sum types, and by using contextual typing information prior to constraint generation, we can add higher-order functions and interfaces that allow finer-grained alias analysis. The formalisation can be found in Appendix B.

Product and Sum Types
Product and sum constructors are checking cases, and must contain only returnable components since we must be able to safely substitute their contents in any context. As with let expressions we can omit annotations on elimination forms, i.e. let ( , ) = in or case of { ↦ → ; ↦ → }, provided that and are used in their continuations, or the sum or product consists of base types.
An advantage of adding product types is that we can avoid nested guard clauses, as we can return both a received value and an updated mailbox name. Figure 12a receives two integers and returns their sum using nested guard expressions, whereas Figure 12b uses products instead.
Since product types can only contain returnable components, they cannot be used to replace -ary argument sequences in function definitions and receive clauses.

Using Contextual Type Information
A co-contextual approach is required to generate the pattern inclusion constraints. Sometimes, however, it is useful to have contextual type information before the constraint generation pass. Consider applying a first-class function: ( ( : Int): Int . ) (5). Although the annotated expression allows us to synthesise a type and use a rule similar to TS-App, the lack of contextual type information means that the approach fails as soon as we stray from applying function literals as in let = ( ( : Int): Int . ) in (5)). A typical backwards bidirectional typing approach requires synthesising function argument types, but this is too inflexible in our setting as each mailbox name argument would need a type annotation.
In the base system a global signature maps message tags to payload types. While technically convenient, this is inflexible. First, distinct entities may wish to use the same mailbox tags with different payload types. For example, a client may send a Login message containing credentials to a server, which may then send a Login message containing the credentials and a timestamp to a session management server. Second, we need a syntactic check on a receive guard to avoid aliasing, as outlined in §2: either the received payloads or free variables in the guard body must be base types. This conservative check rules out innocuous cases such as in Figure 12c, which waits for messages from two actors before signalling them to continue. With contextual information we can associate each mailbox name with an interface , which maps tags to payload types, and allows us to syntactically distinguish different kinds of mailboxes (e.g. a future and its client). Since a name cannot have two interfaces at once, we can loosen our syntactic check on receive guards to require only that the interfaces of mailbox names in the payloads and free variables differ, as typing guarantees that they will refer to different mailboxes.
We implement the above extensions via a contextual type-directed translation: we annotate function applications with the type of the function (i.e.

IMPLEMENTATION AND EXPRESSIVENESS
We outline the implementation of a prototype type checker written in OCaml, and evidence the expressiveness of Pat via a selection of example programs taken from the literature. We first show that using quasi-linear typing in place of dependency graphs ( Neykova and Yoshida [2017b]. Finally, we encode a case study provided by an industrial partner that develops control software for factories.

Implementation Overview
Pat programs are type checked in a six-phase pipeline: lexing and parsing; desugaring, which expands the sugared form of guards (i.e. rewrites free as guard : 1 {free ↦ → ()} and fail as guard : 0 {fail}) and adds omitted pattern variables; IR conversion, which transforms the surface language (supporting nested expressions) to our explicitly-sequenced intermediate representation; contextual type-checking, which supports the contextual extensions from §5.2; constraint generation; and constraint solving. The Pat typechecker operates in two modes that determine how receive guards are type checked. Strict mode uses the lightweight syntactic checks outlined in §3 and §4, whereas interface mode uses interface type information ( §5.2) to relax these checks. This means that every Pat program accepted in strict mode is also accepted in interface mode. More details are given in Appendix C. The examples reveal the benefits of mailbox typing. Runtime checks, such as manual error handling ( §1.2) are unnecessary since errors (e.g. unexpected messages) are statically ruled out by the type system. Mailbox types also have an edge over session typing tools for actor systems, e.g. [Neykova and Yoshida 2017b;Tabone and Francalanza 2022] where developers typically specify protocols in external tools and write code to accommodate the session typing framework. In contrast, mailbox typing naturally fits idiomatic actor programming.

Expressiveness and Typechecking Time
This flexibility does not incur high typechecking runtime (see Tbl. 1). The aim of benchmarking typechecking time is to show that mailbox typechecking is not prohibitively expensive, rather than to claim comparative results. Comparisons with other implementations of (non-mailbox-typed versions of) the benchmarks written in other languages are unlikely to strengthen our results as the benchmark source code would be different, and we would be measuring e.g. Java's entire type system implementation rather than the essence of the typechecking algorithm.

Case Study.
Finally we describe a real-world use case written by Actyx AG 3 , who develop control software for factories. The use case captures a scenario where multiple robots on a factory floor acquire parts from a warehouse that provides access through a single door. Robots negotiate with the door to gain entry into the warehouse and obtain the part they require. The behaviour of our three entities, Robot, Door, and Warehouse is shown in Fig. 13. Our concrete syntax closely follows the core calculus of §3, without requiring that pattern variables in mailbox types are specified explicitly. Type checking our completed case study given in Appendix C.2 relies on contextual type information (see §5), and takes ≈89.6 ms.
We give an excerpt of our Warehouse process (below) that maps the interactions of its lifeline in Fig. 13. In its initial state, empty, the Warehouse expects a Prepare message (if there are Robots in the system), or none (if no Robot requests access), expressed as the guard Prepare + 1 on line 2. When a part is requested, the Warehouse transitions to the state engaged, where it awaits a Deliver message from the Door and notifies the Robot via a Delivered message (lines 9-15). Subsequent interactions that the Warehouse undertakes with the Door and Robot are detailed in Appendix C.2. Note that our type system enables us to be precise with respect to the messages mailboxes receive. Specifically, the guard on line 2 expects at most one Prepare message, capturing the mutual exclusion requirement between Robots, whereas the guard on line 10 expects exactly Deliver.

RELATED WORK
Behaviourally-typed actors. The asymmetric nature of mailboxes makes developing behavioural type systems for actor languages challenging. Mostrous and Vasconcelos [2011] investigate session typing for Core Erlang, using selective message reception and unique references to encode sessiontyped channels. Francalanza [2021, 2022] develop a tool that statically checks Elixir [Jurić 2019] actors against binary session types to prove session fidelity. Neykova and Yoshida [2017b] propose a programming model for dynamically checking actor communication against multiparty session types [Honda et al. 2016], later implemented in Erlang by Fowler [2016]. Neykova and Yoshida [2017a] show how causality information in global types can support efficient recovery strategies. Harvey et al. [2021] use multiparty session types with explicit connection actions [Hu and Yoshida 2017] to give strong guarantees about actors that support runtime adaptation, but an actor can only participate in one session at a time. Using session types to structure communication requires specifying point-to-point interactions, typically using different libraries and formalisms. In contrast, our mailbox typing approach naturally fits idiomatic actor programming paradigms.
Bagherzadeh and Rajan [2017] define a type system for active objects [de Boer et al. 2007] which can rule out data races; this work targets an imperative calculus and is not validated via an implementation. Kamburjan et al. [2016] apply session-based reasoning to a core active object calculus where types encode remote calls and future resolutions; communication correctness is ensured by static checks against session automata [Bollig et al. 2013].
Mailbox types are inspired by behavioural type systems [Crafa and Padovani 2017] for the objective join calculus [Fournet and Gonthier 1996]. The technique can be implemented in Java using code generation via matching automata [Gerbo and Padovani 2019], and dependency graphs can rule out deadlocks [Padovani 2018a], but the authors do not consider a programming language design. Scalas et al. [2019] define a behavioural type system for Scala actors. Types are written in a domain-specific language, and type-level model checking determines safety and liveness properties. Their system focuses on the behaviour of a process, rather than the state of the mailbox.
Session-typed functional languages. Session types [Honda 1993;Honda et al. 1998] were originally considered in the setting of process calculi; Gay and Vasconcelos [2010] were first to integrate session types in a functional language by building on the linear -calculus, and their approach has been adopted by several other works (e.g. [Almeida et al. 2022;Lindley and Morris 2015]). Linear types are insufficient for mailbox typing since we require multiple uses of a mailbox name as a sender; we believe our use of quasi-linearity for behavioural typing is novel, and we conjecture that it could be used to support other paradigms (e.g. broadcast) that require non-linear variable use.
Co-contextual typing. Co-contextual typing [Erdweg et al. 2015] was originally introduced to support efficient incremental type-checking, and has also been used to support intrinsically-typed compilation [Rouvoet et al. 2021]. Padovani [2014] uses a co-contextual type algorithm for the linear -calculus with sums, products, and recursive types; and Ciccone and Padovani [2022] use it when analysing fair termination properties. Backwards bidirectional typing [Zeilberger 2015] is a co-contextual formulation of bidirectional typing, and we are first to use it in a language implementation. Co-contextual typing has parallels with the co-de Bruijn nameless variable representation [McBride 2018], where subterms are annotated with the variables they contain.
Safety via static analysis. Christakis and Sagonas [2011] implement a static analyser for Erlang that detects errors such as receiving from an empty mailbox, payload mismatches, redundant patterns, and orphan messages. All of these issues can be detected with mailbox types, which also allow us to specify the mailbox state. Harrison [2018] implements an approach incorporating both typechecking and static analysis to detect errors such as orphan messages and redundant patterns.

CONCLUSION AND FUTURE WORK
Concurrent and distributed applications can harbour subtle and insidious bugs, including protocol violations and deadlocks. Behavioural types ensure correct-by-construction communication-centric software, but are difficult to apply to actor languages. We have proposed the first language design incorporating mailbox types which characterise mailbox communication. The multiple-writer, single-reader nature of mailbox-oriented messaging makes the integration of mailbox types in programming languages highly challenging. We have addressed these challenges through a novel use of quasi-linear types and have formalised and implemented an algorithmic type system based on backwards bidirectional typing ( §4), proving it to be sound and complete with respect to the declarative type system ( §3). Our approach can flexibly express common communication patterns (e.g. master-worker) and a real-world case study based on factory automation.
Future work. We are investigating implementing mailbox types in a tool for mainstream actor languages, e.g. Erlang; in parallel, we are investigating how languages with first-class mailboxes can be compiled to standard actor languages in order to leverage mature runtimes. We plan to consider finer-grained inter-process alias control, and co-contextual typing with type constraints (as well as pattern constraints), enabling us to study more advanced language features, e.g. polymorphism.

ACKNOWLEDGMENTS
We thank the anonymous ICFP and AEC reviewers for their constructive reviews; our STARDUST colleagues for their helpful comments; and Roland Kuhn for discussion of the case study. This work was supported by EPSRC Grant EP/T014628/1 (STARDUST).

A OMITTED DEFINITIONS
Here we add in the omitted definitions from the main body of the paper.

A.1 Algorithmic environment combination operators
Environment join The environment join operator Θ 1 Θ 2 ▶ Θ; Φ concatenates Θ 1 and Θ 2 , computing the algorithmic type join of any types for overlapping variables, and produces constraints Φ.
The environment merge computes the algorithmic type merge of any overlapping types. If a variable is in one environment but not another, then if it is a base type, it is simply added to the output environment. If it is a send mailbox type ! , then its type is changed to !( ⊕ 1) to denote the fact that it may not be used.
Disjoint environment combination combines two environments; if there are two overlapping types then they must be identical base types.

B DETAILS OF EXTENSIONS
Here we discuss the details of the extensions overviewed in §5.

B.1 Product and sum types
Product and sum types can be added relatively straightforwardly. In both cases, their components must be returnable as otherwise it would not be possible to substitute their contents upon deconstruction.
Product types. We can write the declarative typing rules for products as follows; the rules are unremarkable apart from the requirement that each component of the pair must be returnable in the elimination form.
We can write the corresponding algorithmic rules as follows: Pair construction (TC-Pair) checks that both components have the given types. Environment combination and constraints are handled as usual. Deconstructing the pair in general requires an annotation (TC-LetPair); as with the let rule, we check that the pair has the given annotation and that the types inferred in the environment of the continuation are consistent with the annotation. If both components of the pair are used within the continuation then we can omit the annotation (TC-LetPairNoAnn): the rule first checks that the continuation has the given type, and inspects the resulting environment to construct the product type used for checking .
Sum types. Sum types are similar to product types; again, the declarative rules are unremarkable except for the requirement that sum components must be returnable in the elimination rule.

T-Inl
Γ ⊢ : We can also write the corresponding algorithmic rules: As expected, sum injections are checking cases; similar to the product rules, we also have two separate rules for case expressions which allow annotations to be elided if both and are used within continuations and .

B.2 Contextual Type Information
The other main extension supplements co-contextual type checking with contextual type information in order to enable extensions such as higher-order functions and interfaces. Like signatures in the core calculus, interfaces map message names to lists of types. We modify types , to include interface-annotated mailbox types, as well as -ary function types − → ⋄ − → that are annotated as either linear (□, meaning that the function closes over linear variables and therefore must be used precisely once) or unrestricted (■, meaning that the function only closes over unrestricted variables and therefore can be used an unlimited number of times).
First-class functions. We extend values with fully-annotated, -ary anonymous functions. We require a return annotation since we wish to check the body type, while also synthesising a return type. We opt for an -ary function rather than a curried representation because anonymous functions may only close over returnable values, to ensure they do not violate the conditions on lexical scoping once applied.
Example B.1. Consider the following expression: Here we bind to a function which sends message m to mailbox mb; note that it is used lexically before the guard, which aligns with type combination. However, after reducing the expression (assuming that is chosen as a runtime name), we obtain the following term: After substituting the function body for we now have a second-class use after the first-class use, violating the ordering of returnable and second-class usages.
Mailbox terms. We extend computations so that a user specifies an interface when creating a mailbox (new[ ]). Furthermore, we also augment send and guard expressions with the interface of the mailbox they operate on. Unlike the annotation on new, this does not need to be specified by the user, but instead is added by a straightforward type-directed translation.
B.2.2 Type-directed translation. We propagate annotations to function application and mailbox terms via a contextual type-directed translation.
To do so, we introduce pre-types , : the main difference is that mailbox types do not carry a pattern, but only an interface.

Pre-types
, The type-directed translation pass follows the form of a standard type system for the simplytyped calculus so we omit the rules here. However the judgement has the form Ω ⊢ : ⇝ which can be read "under pre-type environment Ω, term has pre-type and produces annotated term ".

B.2.3 Constraint generation rules.
Finally, we can see how to write constraint generation rules for the extended calculus. The rules in the declarative setting are similar.
Modified constraint generation rules We require three new rules for first-class functions: TS-LinLam types a linear anonymous function by checking that the body has the given result type, and the inferred environment uses variables consistently with the parameter annotations; the rule synthesises a type consistent with the annotation. Further, we require that the inferred environment only closes over variables with returnable types. Rule TS-UnLam is similar, but additionally requires that the inferred environment is unrestricted. Rule TS-FnApp types an annotated function application, checking that the function has the given annotation and that the arguments have the correct types.
As for the rules that support interfaces, rule TS-Send is similar but looks up the types according to the interface rather than the global signature, and checks that the target mailbox has the given interface. Rule TS-New synthesises a mailbox type with the user-supplied interface. Finally, we Special Delivery , , modify the shape of the guard typing judgement to record the interface of the mailbox being guarded upon, and use this to look up the desired payload types in TCG-Recv.

C SUPPLEMENTARY IMPLEMENTATION AND EVALUATION MATERIAL
Our typechecking tool processes Pat programs specified in plain text files that are structured in three segments: (1) Interface definitions that establish the set of messages that a mailbox can receive; (2) Function definitions that specify processes that are instantiable using spawn; (3) Body an invocation of a function defined in (2) that acts as the program entry point.
Pat programs are type checked following the six-stage pipeline of Fig. 14; see §6.1 for an overview.

C.1 Experimental Conditions
We report the mean typechecking time, excluding phases 1-3 of the pipeline. Measurements are made on a MacBook M1 Pro with 16GB of memory, running macOS 13.2 and OCaml 5.0. To ensure minimal variability in our measurements show in Tbl. 1, we preformed 1000 repetition of each experiment. The number of repetitions was determined empirically by calculating the coefficient of variation (CV) [Devore and Berk 2012], i.e. the ratio of the standard deviation to the mean, CV = /¯, for different repetitions until an adequately-low value (< 10%) was obtained.

C.2 Case Study
We give the full Pat program for our factory case study described in §6.2.2. The interaction sequence between our different entities, Robots, Door, and Warehouse, is captured in Fig. 13 and naturally translates to the code that follows.
Interfaces. The messages that Robot, Door, and Warehouse accept are defined by the interfaces: The function main() creates Robot mailboxes, together with a Door and Warehouse mailbox, spawning the respective processes on lines 105-107.
We write fv( ) to return the free variables of a term. Environment subtyping includes a notion of weakening. Read top-down, environment subtyping rules allow us to add a variable with mailbox type !1, replace a type with its subtype, and add in base types. It is therefore useful to introduce a definition referring to the class of types which can be added in through T-Sub which may not be used by a term. We call these types cruft. Cruft is a refinement of un(−) since it also encompasses types which are subtypes of !1.
Definition D.1 (Cruft). A type is cruft, written cruft( ), if either is a base type, or irrelevant( ). A usage-aware type is cruft if = • and is cruft.
It helps to define a stricter version of environment subtyping which does not permit weakening: Definition D.2 (Strict environment subtyping). An environment Γ is a strict subtype environment of an environment Γ ′ , written Γ ≼ Γ ′ if Γ ≤ Γ ′ and dom(Γ) = dom(Γ ′ ).
We extend cruft(−) to type environments and usage-aware type environments in the usual way.
Let us also use Π to range over type environments. A crucial lemma for taming the complexity of environment subtyping is the following, which allows us to separate the type environment required for typing the term from the cruft introduced by environment subtyping. Lemma D.4. If Γ ⊢ : , then there exist Π 1 , Π 2 , Π 3 such that: Proof. Follows from the definition of environment subtyping: read top-down, each application of environment subtyping will either add a variable with an unrestricted type, or alter the type of an existing variable. □ The substitution lemma is only defined on disjoint environments: we should not be substituting a name into a term where it is already free. This is ensured by distinguishing between returnable and second-class usages of a variable: if a variable is returnable, then we know it cannot be used within the term into which it is being substituted. If a variable is second-class, then there will be no applicable reduction rules which result in substitution. Lemma D.5 (Substitution). If: • Γ 1 , : ⊢ : Proof. By induction on the derivation of Γ 1 , : ⊢ : . □ Lemma D.6 (Subtyping preserves reliablility / usability [de'Liguoro and Padovani 2018]). If ≤ , then: (1) reliable implies reliable (2) usable implies usable Corollary D.7. If Γ 1 ≤ Γ 2 then: (1) Γ 1 reliable implies Γ 2 reliable (2) Γ 2 usable implies Γ 2 usable Lemma D.8 (Balancing [de'Liguoro and Padovani 2018]). If m ⊙ ⊑ where ̸ ⊑ 0 and / m is defined, then ⊑ / m. Lemma D.9. If ≤ and returnable( ), then returnable( ) Proof. Follows from the fact that • ≤ •. □ Corollary D.10. If Γ 1 ≼ Γ 2 and returnable(Γ 2 ), then returnable(Γ 1 ).
Proof. By case analysis on the derivation of Γ ⊢ : . □ Proof. Follows from the definition of usage combination: the operation is not symmetric for returnable mailbox types, so the returnable mailbox type must be the last occurrence of that name in the combination. For base types, the definitions of combination for ⊲ and + coincide. □ Lemma D.13. If Γ 1 ⊲ Γ 2 is defined, with Γ 1 and Γ 2 sharing only variables of base type, then Γ 1 + Γ 2 is defined.
Proof. For each such that : ∈ |Γ| and : ∈ Δ, since |Γ| ⊲⊳ Δ is defined, we have that ⊞ is defined. The result then follows from the definition of ⊲, noting that all types in ⌈Δ⌉ are usable and therefore combinable with any other usage. □ Proof. Since irrelevant(Γ 1 ), we have that for each : , it is the case that = ! • where ⊑ 1. □
Proof. By induction on the derivation of C ≡ D, relying on Lemmas D.16 and D.17 and TC-Subs. □ Theorem 3.11 (Preservation). If ⊢ P, and Γ ⊢ P C with Γ reliable, and C −→ P D, then Γ ⊢ P D.
Proof. By induction on the derivation of Γ ⊢ C.

Case E-App
Assumption: Since we also assume ⊢ P, we know by definition typing that: − −− → : ⊢ P : Thus we can recompose: as required.

Case E-New
Assumption: as required.
Case E-Send By Lemma D.4 we have that: Therefore we have that Π 3 = Π ′ 3 , : !m such that: as required.
From that, we can construct the following derivation: as required.

Case E-Recv
Assumption: where D is the following derivation: . By T-GuardSeq and TG-Recv, and since ⊨ ty , we have that ty = 1 ⊕ · · · ⊕ , where 1 = m ⊙ ′ and ′ ≃ ty / m. Furthermore: Now, by the definition of ⊲⊳, we know that env = m ⊙ pat for some pattern pat . By the definition of ⊲, we also know that ∉ dom(Γ 4 ). By Lemma D.8, we have that pat ⊑ / m, and thus ?
Equational reasoning: as required.

Case E-Nu
Follows immediately from the induction hypothesis.

Case E-Par
Follows immediately from the induction hypothesis.

Case E-Struct
Follows immediately from Lemma D.23 and the induction hypothesis. □

D.2 Progress
Lemma D.24 (Canonical forms). If · ⊢ C then there exists some D such that C ≡ D and D is in canonical form.
Proof. Follows from the structural congruence rules. Proof. By case analysis on the derivation of Γ ⊢ , Σ and inspection of the reduction rules. □ Theorem 3.16 (Partial Progress). Suppose ⊢ P and · ⊢ P C where C is in canonical form: Then for each , either: is a value and Σ = ; or • waiting( , , m ) where M does not contain a message m for and where − → are the guard clauses of .
Proof. Functional reduction enjoys progress (D.25), and the constructs new, spawn , and ! m[

− →
] can all always reduce. Therefore, the body of an irreducible thread , Σ must be waiting for a message m on some name . To be waiting, name must be returnable, and therefore cannot occur free in or Σ . It cannot be the case that message m for is contained in M (in which case the configuration could reduce), so typing ensures that is free in one of the other threads. Extending this reasoning we see that if a configuration contains irreducible non-value threads, then C must contain a cyclic inter-thread dependency. □

D.3 Algorithmic Soundness
A solution for a set of constraints is also a solution for a subset of those constraints. Lemma D.26. If Ξ is a solution for a constraint set Φ 1 ∪ Φ 2 , then Ξ is a solution for Φ 1 .
Proof. By mutual induction on the two derivations, noting that whenever a pattern variable is introduced fresh, it is always added to the constraint set. □ Application of a usable substitution preserves algorithmic subtyping in the declarative setting.
Proof. By case analysis on the derivation of ≤ ▶ Φ. □ As a direct corollary, we can show that constraints generated by equivalence preserve subtyping in both directions.
Proof. By case analysis on the derivation of unr( ) ▶ Φ, noting that cases are undefined for linear types, and the result follows straightforwardly for base types.
We now turn our attention to the relation between the algorithmic join and type combination operators.
Proof. By case analysis on the derivation of 1 2 ▶ ; Φ.
Proof. By case analysis on the derivation of 1 ⊓ 2 ▶ ; Φ. For two mailbox types 1 and 2 , since • ≤ • it is always the case that max( 1 , 2 ) ≤ 1 and max( 1 , 2 ) ≤ 2 , so therefore it suffices to consider the non-usage-annotated merge 1 ⊓ 2 ▶ ; ∅ Therefore, since output mailbox types are contravariant in their patterns, it follows that both: as required.
Proof. For it to be the case that ⊨ it must be the case that = 1 ⊕ · · · ⊕ where ⊨ lit for ∈ 1.. .
It suffices to consider the case where we have some = m ⊙ ′ where ̸ ⊑ . In this case, the following must hold: and by the definition of pattern residual and the fact that m ̸ ⊑ it must be the case that / m ≃ 0. Consequently we know that ≃ 0.
To ensure that ⊨ we need to show ⊨ and therefore that 0 ≃ / m , which follows by the definition of pattern derivative as required. □ Algorithmic soundness relies on the following generalised result: Lemma D.39 (Algorithmic Soundness (Generalised)).
Proof. By mutual induction on all statements. We inline our proof of statement 4 with TC-Guard.
We know in all cases that the solution covers the pattern variables in the program, return type, and constraints. Therefore by Lemma D.27 we know that any produced environment will contain pattern variables contained in the solution. We make use of this fact implicitly throughout the proof.

Case TS-Unit
Similar to TS-Base.

Case TS-New
Similar to TS-Base.

Case TS-App
Assumption: We can also assume that there exists some Ξ which is a usable solution of Φ prog ∪ Φ 1 ∪ . . .

Case TC-Let
Assumption: We also assume that we have some usable solution Ξ for Φ 1 ∪ · · · ∪ Φ 4 , and by Lemma D.26, we know that Ξ is a usable solution for all Φ individually.

Case TC-Guard
Assumption: Since guards must be unique we know that there will be at most one fail branch in − → . Without loss of generality assume that 1 = fail (the order of guards does not matter, and the argument is the same if there is no fail guard).
Let us assume without loss of generality that > 1 (i.e., fail is not the only guard). Thus we have that: By repeated use of the induction hypothesis (statement 3), we have that Ξ(Θ ) ⊢ : Ξ( ) :: where ⊨ lit for 2 ≤ ≤ .
Since Ξ is a usable solution of the constraint set we have that ⊑ . Now since ⊨ and ⊑ , by Lemma D.38 we have that ⊨ . By Lemma D.37, we have that there exists some Γ such that Γ ≤ Θ for each . Thus, by T-Sub, we can show: Γ ⊢ : Ξ( ) :: .
It remains to be shown that ⊨ lit m ⊙ ( / m): The pattern residual and concatenation cancel, so the premise holds and therefore we can conclude that ⊨ lit m ⊙ ( / m).
Finally, we can reconstruct using TG-Recv:

D.4 Algorithmic Completeness
Every Γ is also a valid Θ and every is a valid . We will therefore allow ourselves to use Γ and in algorithmic type system derivations directly.
D.4.1 Useful auxiliary lemmas. We begin by stating two useful results. The first lemma states that values are typable in the algorithmic system without constraints.
Proof. The proof is by induction on the derivation of Γ ⊢ : .
Proof. Follows from the definition of TC-Subs, noting that the subtyping constraint is instantiated as ≤ ▶ Φ. There are two ways we can create a derivation of ≤ ▶ Φ: either if = and we have ≤ ▶ ∅, or if is a mailbox type (take an output mailbox type here, although the reasoning is the same for an input mailbox). In this case, we would have a derivation of ! ≤ ! ▶ <: . Since <: is a tautology, it follows that we need not add an additional constraint and can show ! ≤ ! ▶ ∅.
Using TC-Subs we can construct: as required. □ D.4.2 Completeness of auxiliary definitions. We now need to show completeness for all auxiliary judgements (e.g., subtyping, environment combination).
In this case we can construct a derivation: Using algorithmic type joining, we can construct the following derivation: At this point we know that the domains of Ξ 1 and Ξ 2 are disjoint. Let us construct Ξ = Ξ 1 ∪ Ξ 2 ∪ ↦ → . It remains to be shown that Ξ is a solution; it suffices to show that (Ξ( ) ⊙ ) ⊑ Ξ( ). By the transitivity of pattern inclusion we have that (Ξ( ) ⊙ ) ⊑ ( ⊙ ) ⊑ Ξ( ) We have that Ξ(? ) = ? and therefore we have that (trivially) ? ≤ ? with Ξ ⊃ Ξ 1 ∪ Ξ 2 a solution for the constraint set, as required.
Proof. By case analysis on the structure of . Base types follow directly, so we need instead to examine mailbox types.

Case = ?
In this case, by the definition of subtyping, we have that: The reasoning for usage subtyping follows from the previous case, so we take for given that ≤ max( 1 , 2 ). Next, we construct the following derivation using algorithmic type merging: fresh ? ⊓ ? ▶ ? ; { <: , <: } Since Ξ(? 2 ) = ?Ξ( ) 2 we can assume: Using algorithmic subtyping we can derive: 1 ≤ 2 ? ≤ ? ▶ <: ? 1 ≤ ? 2 ▶ <: And since Ξ( ) ⊑ Ξ( ) it follows that Ξ is a usable solution of <: , as required. D.4.3 Supertype checkability. In order to show the completeness of T-Sub, we must show that if a term is checkable at a subtype, then it is also checkable at a supertype.
To do this we require several intermediate results.
We firstly define closed and satisfiable constraint sets.
If we have two types which do not contain pattern variables, algorithmic subtyping does not introduce any pattern variables into the constraint set.
Proof. A straightforward case analysis on the derivation of ≤ ▶ Φ. □ Next, if we have an algorithmic subtyping judgement which produces a satisfiable constraint set, and a subtyping relation with a supertype, then we can show that the algorithmic subtyping judgement instantiated with the supertype will produce a satisfiable constraint set.
Proof. By case analysis on the derivation of ≤ ′ ▶ Φ. Base types hold trivially, so we need only consider two cases: Case ! ≤ ! Assumption: also we know that <: is satisfiable (therefore that ⊑ ), and ! ≤ . By the definition of subtyping we have that = ! ′ for some pattern ′ , and therefore that ′ ⊑ .
Case ? ≤ ? Assumption: 1 ≤ 2 ? 1 ≤ ? 2 ▶ { <: } also we know that <: is satisfiable (therefore that ⊑ ), and ? ≤ . By the definition of subtyping we have that = ? ′ for some pattern ′ and therefore that ⊑ ′ . Thus by transitivity we have that ⊑ ⊑ ′ and therefore that: where <: ′ is satisfiable, as required. □ We also need to show that environment joining respects subtyping, which we do by firstly showing that type joining respects subtyping.
Relying on the previous results, we can now show the supertype checkability lemma.
Proof. By mutual induction on the three premises. We concentrate on proving premise 1 in detail, and TCG-Recv for premise 3; premise 2 follows from premise 3, and the remaining guard cases are straightforward.
By induction on the derivation of ⇐ ▶ Θ; Φ.

Case TC-Var
Assumption: ⇐ ▶ : ; ∅ Now given that we have ≤ , we can construct: ⇐ ▶ : ; ∅ As Φ = · it straightforwardly follows that Ξ is a usable solution, and since ≤ we have that : ≤ : as required.
Proof. By induction on the respective derivation, noting that since the signature and types are closed, pattern variables are only introduced through the type join and type merge operators, where they are created fresh. □ D.4.5 Completeness proof. Finally, we can tie the above results together to show algorithmic completeness.
Proof. By mutual induction on both premises.

Case T-Guard
Since pattern variables are generated fresh, we have that the pattern variables for each Ξ are disjoint. Therefore, we have that: as required.