Foundation A collection of thougts and crazy Swift ideas

The Box Pattern: A Deep Dive into Swift's Hidden Gem

Dick In The Box

So, I think everybody has occasionally used this kind of type. It’s so old that we can trace its existence back to the C++ and Java eras. A box type refers to a container (or a wrapper, if you prefer) that holds a value indirectly, typically by storing a pointer or a reference to a value.

This clever idea solved a problem in reference vs. value type semantics and immutable state. It’s true that it also saves us when we need to deal with heterogeneous types in vectors or arrays. One use case of Boxes is to store value types inside of reference types, allowing us to mutate the state of the stored type (call by semantics). So box types allow us to mutate state via interaction for both types described previously.

Box types bridge the gap between value semantics (copying data on assignment) and reference semantics (copying references so multiple variables share the same data). Historically, Lisp and Algol introduced “boxes” to enable assignment and references in primarily value-semantic languages, letting a function modify a caller’s variable by passing a pointer-like container. In Scheme and Racket, for example, box(val) explicitly wraps a value for mutability, with unbox(box) to read and set-box! to update. This design simulates pass-by-reference without requiring pervasive reference semantics, beneficial for controlled mutation in an otherwise functional ecosystem.

Pitfalls, Anti-Patterns, and Alternatives in Swift

While box types are powerful, there are some pitfalls and alternatives to consider:

  • Overusing Reference Semantics: One must be cautious not to abuse boxes in a way that undermines Swift’s value semantics advantages. Value types help local reasoning (you know that modifying one copy won’t affect another). If you start wrapping many things in boxes to share state, you can end up with a more complex program where changes in one place have far-reaching effects (aliasing everywhere). This can lead to bugs similar to those in purely reference-based languages.

    Anti-pattern: wrapping every model in a Box to avoid copying or to make everything mutable. Instead, prefer value semantics unless you truly need sharing. Use boxes intentionally and document their usage (so it’s clear that a particular property is a shared reference).

  • Memory and Performance: Each box is a heap allocation. For small values, allocating a box can be much slower than just copying the value. For example, an Int is 8 bytes and extremely fast to copy; boxing it requires a heap allocation (which might be ~16-24 bytes overhead plus the 8 bytes, and a potential cache miss to dereference). So if you put many small values in boxes for no good reason, you could hurt performance and increase heap fragmentation. Swift’s optimizer can sometimes stack-promote small objects (escape analysis), but you shouldn’t rely on that. So, use boxes only when needed. In high-performance code, one often prefers value types and avoids the heap where possible.

  • Unintended Retention and Leaks: Because a box is a reference, it extends the lifetime of the value until the box is deallocated. This can sometimes inadvertently cause memory leaks or retain cycles. For instance, if an ObservableBox’s listener closure captures the box itself (directly or indirectly), you can form a retain cycle (box holds listener -> listener captures box). In our earlier ObservableBox example, we might use [unowned self] in the closure if it refers to the box, to break such a cycle. Always be mindful of reference cycles when you have closures or multiple objects referring to each other. Using weak references inside boxes (if the box holds an object) is an option to prevent strong cycles, with the outside world.

  • Alternatives – Property Wrappers and Specialized Containers: Before reaching for a generic Box class in Swift, consider if a higher-level construct exists. Property Wrappers can often encapsulate a pattern more cleanly. For example, instead of manually using a Box for state, we use @State. Instead of a Box for a weak reference, some use @Weak var foo = object. You can even make your own property wrapper that uses a box internally but presents a nicer API. Another alternative for observable state is to use Combine’s @Published on a class property or Combine subjects externally. Combine’s subjects (PassthroughSubject, CurrentValueSubject) are thread-safe and come with a well-defined subscriber model, which might be preferable to a homemade Box when things get more complex (e.g., needing to handle threading or multiple subscribers with completion, etc.). With Swift’s new concurrency, you might also consider an Actor to isolate state instead of a Box – an actor can hold a value, and you can query or update it asynchronously, ensuring thread safety without manual locks or sendable annotations.

  • Standard Library or Community Implementations: Although trivial to implement, it’s worth knowing if others have already solved your problem. For weak references, there are packages (like one simply called “Weak”) that provide a Box class for weakly holding a value. For type erasure, Swift’s standard library provides AnySequence, AnyCollection, etc., so you might not need to implement an AnyBox yourself unless it’s a very custom protocol. Always weigh adding yet another class with all the reference semantics versus using language features (like inout for passing by reference temporarily, or closure capturing, which might be simpler).

In Swift’s value-oriented world, box types are a valuable escape hatch to get reference semantics when necessary. They underpin many important patterns (from CoW optimizations to UI state management). Understanding the design trade-offs – when to use a box vs. when to stick with value types or use a higher-level abstraction – is crucial. Use box types to encapsulate mutable or shared states in a controlled way, and you’ll get the benefits of indirection and flexibility. But avoid turning your codebase into an overly reference-ridden design without reason, as that can negate some of Swift’s safety and clarity gains.

In conclusion, box types are pretty cool because they allow us, the developers, to change values or references through indirection. From their origins in enabling reference semantics in languages, to practical uses in decoupling components and managing state, they provide a simple yet profound tool. Different languages offer different idioms – be it a Box<T> class in Swift, smart pointers in C++, or auto-boxed wrapper objects in Java – but they all serve the purpose of giving data an identity and life beyond a single scope or value copy. By using box types judiciously, developers can achieve patterns like observable properties, interior mutability in immutable contexts, and performance optimizations, all while being mindful of the complexity that reference semantics introduces. The key is recognizing those scenarios where a box is the right tool and implementing it in the idiomatic way the given language expects. With the guidance and examples above, one can leverage box types to write flexible and robust code across various programming paradigms.

Parameter Packs and Heterogeneous Boxes

But what are the reasons for this post?

Well, Swift 5.9 introduced a new feature called Parameter Packs. Parameter Packs are really powerful and useful. And somehow they introduced a new kind of box I like to call the “heterogeneous box.” For people who are not familiar with the concept of Parameter Packs, they work like variadic generics. Let’s see the example below:

func <each P>packs(argument: repeat each P) -> (repeat each P) {
    return (repeat each argument)
}

What does this sample do? Well, it basically returns a tuple of any size for the parameters passed to the function. Let’s say you use the function:

let f = packs()  // Returns ()

let t = packs("Leonard Nimoy", 1, true) // Returns ("Leonard Nimoy", 1, true)

Okay, but tuples are kind of a weird type to deal with. What else can we do?

Let’s see this sample from WWDC:

func query<Payload1, Payload2, Payload3>(
  _ item1: Request<Payload1>,
  _ item2: Request<Payload2>,
  _ item3: Request<Payload3>
) -> (Payload1, Payload2, Payload3)

We can also iterate in value packs like this:

struct Request<Payload> {
  func evaluate() -> Payload
}

func query<each Payload>(_ item: repeat Request<each Payload>) -> (repeat each Payload) {
  return (repeat (each item).evaluate())
}

Okay, but where is the new fancy HeterogeneousBox? Let’s check it out:

struct HeterogeneousBox<each Value> {
    private(set) var value: (repeat each Value)
    
    init(_ value: repeat each Value) {
        self.value = (repeat each value)
    }
    
    init(_ tuple: (repeat each Value)) {
        self.value = tuple
    }
}


let box = HeterogeneousBox(1, "Leonard Nimoy", 3.14, true)
print(box.value) // Prints  1, "Leonard Nimoy", 3.14, true

This is essentially the same concept of boxes discussed earlier, but there are a few gotchas when dealing with Parameter Packs and value packs. For instance, for the property value, you can’t access the first parameter using value.0.

However, there are some tricks that can make this possible. I believe in Swift 5.4 Apple introduced a feature called @dynamicMemberLookup that allows one to look for properties or keypaths at runtime; it was extremely useful when Apple was working with Google and TensorFlow. By adding this annotation, we’re able to access tuple.0 (if it exists, remembering it is runtime). Here is the implementation:

@dynamicMemberLookup
struct HeterogeneousBox<each Value> {
    private(set) var value: (repeat each Value)
    
    init(_ value: repeat each Value) {
        self.value = (repeat each value)
    }
    
    init(_ tuple: (repeat each Value)) {
        self.value = tuple
    }
    
    // By using WritableKeyPath we can access the value of the tuple and set a new one (of the same type)
    subscript<Output>(
        dynamicMember keyPath: WritableKeyPath<(repeat each Value), Output>
    ) -> Output {
        get { value[keyPath: keyPath] }
        set { value[keyPath: keyPath] = newValue }
    }
}

    
let box = HeterogeneousBox(1, "Leonard Nimoy", 3.14, true)
print(box.value) // Prints  1, "Leonard Nimoy", 3.14, true

box.0 = 2
print(box.value) // Prints  2, "Leonard Nimoy", 3.14, true

I’ve created a repository with all the things proposed in this post and I’ve also implemented some protocol conformances and other features.

Other Experiments:

  • TypeInferredFactory - I was attempting to create an ultra-generic factory pattern using this feature and macros, but I didn’t have time to finish it.

Dealing with Swift Parameter Packs is quite an adventure. I propose you check out the repository of the HeterogeneousBox and check the source code to see what kind of madness is required to achieve some things. It was quite a ride. If you are interested in this topic or want to give any feedback about the code in the repositories, feel free to contact me or open a PR on GitHub.