I ran into a simple use-case for wrapping a large value type in a heap allocation recently; not necessarily Box in its current form, just anything roughly similar would've sufficed.
A few months ago I was trying to optimize my program so I could attempt some larger workloads, and I wanted to see if noncopyable types and InlineArray could help. The basic premise here that it spins up a large number of Tasks, and each Task creates a struct RewritingSystem to do some work. This work involves allocating and deallocating a large number of Array<UInt8> values, but since the total amount of work done in each RewritingSystem is relatively small and bounded, I wanted to try refactoring the algorithm to perform bump pointer allocation into an InlineArray instead, and store all temporary results there, to avoid allocation and reference counting overhead.
I made RewritingSystem: ~Copyable and added an InlineArray<16384, UInt8> stored property, but of course I immediately got a stack overflow, because the stack size seems to be pretty small in async functions. I was able to proceed by changing RewritingSystem into a class, but I could have also used something like Box (or an indirect struct, etc).
(Unfortunately I didn't get very far because the next problem I ran into was that SILGen really likes to copy stored properties when they're accessed, so once again you end up copying 16kb of data needlessly and blowing the stack. The other issue was that I was representing "pointers" into my arena as integers, and it seemed that the overhead of bounds checks on every single access into the arena seemed to negate any performance gains. I hope to make another attempt at some point, perhaps once the implementation of these features is further along. Or perhaps I should just go all the way and use Unsafe*Pointer types to interact with my arena instead.)
+1000
If I'm doing something that calls for a non-copyable, heap-allocated box, I might not even mind a little bit of syntactic noise to inform the reader that something funky is going on.
If this Box has to be clear about its semantics, then the name should be UniqueHeapRef or UniqueHeapReference, which is clear and descriptive because it conveys three essential pieces of information:
Unique: This indicates that the value has unique ownership.
Heap: It tells you the value is stored on the heap.
Ref: It indicates that the type is a reference, not a copy or unsafe pointer, and avoids the implication of raw memory manipulation.
By using “Unique” and “Ref,” it makes the ownership model explicit. It’s a unique, owned reference to a value on the heap, and this captures both the safety and semantics of the type.
Compared to names like HeapBox or RefBox , UniqueHeapRef is more precise and targeted. It leaves no ambiguity about the type's behavior.
Its clarity makes it a better choice for both developers reading the code and those maintaining it in the future. It’s a good fit to Swift rule clarity is more important than brevity.
Current Box implementations, made as a class wrapper, are also references, but they are not unique and can be copied.
While standard library has special shadowing rules which prefer user defined types by
default, it would be not clear which box is is used by inspecting the sources. This new type has more specific semantics, that should be reflected in its name.
I am also thinking whether 2 additional overloads of init should be done:
struct UniqueHeapRef<Value: ~Copyable>: ~Copyable {
init(_ initialValue: consuming Value) { // standard init
...
}
}
extension UniqueHeapRef where Value: AnyObject {
@available(*, unavailable, message: "Storing references to Object instances are not supported")
init(_ initialValue: consuming Value) { // unavailable
...
}
}
extension UniqueHeapRef where Value == AnyObject {
@available(*, unavailable, message: "Storing references to AnyObject instances are not supported")
init(_ initialValue: consuming Value) { // unavailable
...
}
}
I see no harm to store a reference to another reference, but I also see no practical reasons to allow it and to do so.
The only case is a generic context, where some T can be both of value or ref type. But from my experience ref boxes are not needed in such contexts. We can lift this restriction if meaningful use cases are found.
A silly thought, but isn't the existence of a heap or a stack an implementation detail of the compiler and/or underlying system, and not the language itself?
If you prefer, you can instead think of it this way: structs and non-indirect enums store their contents as part of the value, whereas indirect enums and classes store their contents out-of-line. (Note that this doesn't mean that structs and non-indirect enums are always on the stack; for example, if a stored property of a class is a struct, then that struct value will live on the heap, but it's part of the enclosing class instance.)
Even if you ignore implementation effects, the semantic difference here is that out-of-line storage allows you to construct infinite types, for example:
enum List<T> {
case empty
indirect case element(T, List<T>)
}
Without the indirect, your List<T> would directly contain itself, so you'd end up with infinitely many instances of T, and it would have an infinite size, and not be representable.
In practice though, knowing if something will end up on the heap or the stack is very important, because stack space is a more limited resource than heap. So yes, its an implementation detail in some sense, but a very important one.
(Also, in theory, a Swift compiler could be constructed which heap-allocates activation records for subroutine calls, the way some Scheme and ML implementations do, in order to support general continuations for example. In this case, the concept of stack allocation would become much murkier. But it would still matter, I think.)
I think there's a bikeshedding point about how "heap" is somewhat of a generic trademark of a term to refer to dynamically allocated memory and that "heap" also waves in the direction of a precise allocation implementation.
The C++ name, unique_ptr, which we would obviously spell UniquePointer, seems to fit in pretty well with the existing Swift type hierarchy. It would also imply that the value be accessed via pointee, answering that bikeshed as well.
I was on-board with Box originally, since it does match Rust's type pretty accurately, but as saagarjha points out, due to other features of Swift it doesn't have nearly the general applicability of Rust's Box, and as others point out, Box commonly means something else entirely in the Swift ecosystem.
I personally don’t love Ref or Pointer names because all the other uses of those are non-owning—that’s one of the reasons why UnsafeBufferPointer wasn’t abbreviated to UnsafeBuffer. “Unique” implies that if you think about it, but it’s still implicit.
Also, any “Pointer” I would expect to be able to interchange with UnsafeRawPointer…and maybe I can. That’s not how Mutex works though.
You can, but now you have two allocations where only one is required; the Swifty equivalent of Rust's Arc is something like
final class ARC<Value: ~Copyable> {
init(_ value: consuming Value)
subscript () -> Value { borrow mutate } // or whatever Box ends up with
}
extension ARC: Sendable where Value: Sendable {}
I’m generally supportive of this proposal. However, I’m hesitant about the method name clone().
While I understand the motivation to align with a potential future Clonable protocol, adopting this name now feels like it sets a specific precedent for a feature that hasn't been fully explored yet.
I have a few concerns with stabilizing clone() at this stage:
Naming Flexibility: We might eventually decide that Clonable isn't the right name for the protocol (e.g., we might prefer ExplicitlyCopyable or something entirely different) or that the method should be named differently. Using clone() here leans heavily in one direction before that debate has happened.
Namespace Pollution: If Copyable eventually refines this future protocol (as suggested in Future Directions), we risk a future where every copyable type gains a redundant .clone() method. This could clutter the namespace and clash with existing user-defined methods. We would likely need disambiguation rules, compiler quirks to hide clone() when conformance to Copyable is visible, or linters to catch redundant calls to clone().
Previous discussions regarding explicit copying explored using a copy operator (copy x) to mirror consume and borrow. If the language adopts a copy operator for types conforming to this new protocol, Box having a .clone() method would create an awkward inconsistency in vocabulary.
I'd suggest we stick to the terminology we have today - "copy" - either as an initializer or a method.
extension Box where Value: Copyable {
public init(copying source: borrowing Box<Value>)
// or
public func copy() -> Box<Value>
}
Personally, I prefer the initializer approach. If Swift adopts a Deref-like mechanism (as mentioned in Future Directions), Box having a method named copy() creates potential ambiguity if the wrapped value also has a copy() method. An initializer avoids this collision entirely.
Having been using this from the swift-collections package quite extensively, I think this fits perfectly in Swift! Name is great, the subscript is extremely quick to get used to and doesn't prevent future Deref-like direction.
I'll add that Box also fits with prior art of "boxing" values, doing exactly the thing you hear about when going around C# and Java, without the GC because this is Swift of course.
I'd prefer a shorter name and the subscript syntax for much the same reasons syntax sugar was added to InlineArray, it's a lot of ceremony and if you're using this type, you're most likely closer to an expert than a beginner. Same opinion as Kyle for this, UnsafeMutableRawBufferPointer?.baseAddress?.pointee gets old real quick and needlessly obfuscates already complex call sites, I'd rather shorten the names with Box(myValue)[].
Sure, you can imagine exotic representations where the size of a value can vary, etc. But then it’s not clear how mutation, or even just field access, would work while maintaining the same time complexity guarantees that Swift makes today, and time complexity of fundamental operations is definitely part of the language semantics and is not just an implementation detail.
Question about future applications in Standard Library
Not trying to get too far ahead of ourselves… but would this Box work as the underlying data storage of a potential HypoArray from SE-0437? Which would then work as the underlying data storage of a potential SmallArray from SE-0453?
I've deliberately avoided talking about the heap in the parts of the book I've worked on because we've never formalized that as a language concept, as far as I'm aware. Even in the quoted discussion of error handling, dynamic memory allocation and the heap aren't mentioned as language features, but as a description of that code's behavior at run time.
Another "for what it's worth", TSPL doesn't have any examples of the box[] syntax, and its discussion of subscripting in the guide and reference doesn't mention that a zero-argument subscript is possible. Tracing the formal grammar, even that requires at least one argument. (Specifically, self-subscript-expression uses function-call-argument-list — for actual function calls, the () form is produced by function-call-argument-clause.)