Good catch! Worth fixing the grammar doc.
Here is the relevant fragment:
Note the difference in the grammar for function calls to allow for an empty list:
Good catch! Worth fixing the grammar doc.
Here is the relevant fragment:
Note the difference in the grammar for function calls to allow for an empty list:
+1 on this being a property wrapper if the main use case is that you’ll interact with the wrapped value, and only rarely interact with the Box type itself. A property wrapper seems like a great fit because it keeps the concerns about the property’s storage/retrieval mechanism isolated to the declaration site, and everywhere else, you can use it like a normal property (with compiler help if you try to do something non-obviously naughty).
No, Box allocates 1 element on the heap. A HypoArray, like the RigidArray and UniqueArray recently introduced in swift-collections, will just manage their own pointer.
The point of a SmallArray/TinyArray is to be allocated inline on the stack when the count of elements is less than or equal to a fixed amount, and allocate on the heap for more, so again Box isn't a fit as it always allocates on the heap.
This is a common practice in high performance code where a low amount of elements is common. The prime example of this in Swift is String where it usually stores 15 utf8 bytes inline (+1 metadata byte), and allocates on the heap for bigger strings.
See my very new project (swift-tiny-sequence) trying to put such a "SmallArray" together until there is such a primitive in std-lib (I'm calling it TinySequence for now), which right now uses some integers as inline storage, and uses RigidArray/UniqueArray as the heap storage.
Hi folks,
I’ve updated the proposal to go with a named property value to access the underlying value within the box instead of the empty subscript. I’ve also clarified the accessors used for this property, made it more obvious that I’m not proposing a Clonable on top of Copyable, as well as some more discussion about needing to indirect values sometimes to improve performance or code size.
You can find the updated proposal here: Box by Azoy · Pull Request #3067 · swiftlang/swift-evolution · GitHub
Thanks @Alejandro! Can the proposal address why the alternative of a property wrapper wasn't considered?
This is true, but in the example of building a linked list from Ben Cohen here: Noncopyable Generics in Swift: A Code Walkthrough
He produces a List type defined like:
struct List<Element: ~Copyable>: ~Copyable {
struct Node: ~Copyable {
var element: Element
var next: Link
}
typealias Link = Box<Node>?
var head: Link = nil
}
and uses Optional as the enum of choice here to wrap the box in which is not an indirect enum. This design certainly could’ve defined its own Link type that was explicitly indirect, but now it loses out on all of the API that already exists on Optional.
I am not sure how that would work out. Would something like this not be possible with indirect support for struct properties?
struct List<Element: ~Copyable>: ~Copyable {
struct Node: ~Copyable {
var element: Element
indirect var next: Node?
}
var head: Node? = nil
}
I still think a box type can be handy, but I think the majority of cases would be covered by expanding the support of this existing language feature. Another benefit is that indirect properties don’t require access via .value or [] or some other unwrapping syntax.
It would be possible, but indirect properties are an entirely new language feature as opposed to indirect enums which already exists but could just be updated to support noncopyable enums and associated values.
I would personally prefer to not proliferate property wrappers in the language any more than they need to. Macros at least would allow for source expansion and better tooling. I’m not sure if the standard library is capable of defining macros though ![]()
I concur that property wrappers are not ideal, and they're especially problematic in environments sensitive to performance and binary size, like Embedded Swift.
A macro seems more reasonable, and we already have a couple of macros in the stdlib, don't we? I thought something like @TaskLocal was converted from PW to a macro recently?
Why would a macro be more reasonable? It would generate essentially the same code as a wrapper, so the performance and binary size impact would be the same, with the added downside of potentially significant build time impact that scales with the number of usages.
A property wrapper also incurs the overhead of a nominal type declaration, which means emitting a nominal type descriptor and metadata.
While this is not official, I think of property wrappers as being in the same bucket as rethrows; not exactly deprecated, but almost completely subsumed by a more general feature, and probably best to avoid in new code. (I might be wrong about the “almost completely subsumed” part though.)
If Box<T> were itself the property wrapper type, then I think that extra net metadata could be avoided. (Though someone would probably have to do some compiler work to make property wrappers of non-Copyable/Escapable type work. The extra members might also pollute Box's otherwise minimal interface as well.)
It feels to me like this sits on an edge between "language" and "library". I don't think that having indirect (works with enums only) and Box (works in other places, but is more verbose to use) is a good decision (it might be an expedient decision, but I think swift evolution should strive for more than expedience!)
If we decide it should come down on the side of "language", then we should expand indirect to do what we want here, and not surface the Box type at all. Presumably that would mean allowing indirect on any property or associated value to specify that it gets a heap box, and allowing indirect on structs to allow them to infer indirect on fields that would otherwise be self-referential. This approach would also solve the "conditionally-copyable" problem.
If we decide it should come down on the side of "library", then we should make it ergonomic to use in existing places that use indirect, and then deprecate indirect in favor of the new, more general mechanism. That probably means allowing @Boxed or @Indirect as a property wrapper or macro, and ensuring that that syntax is legal in an enum's associated values. It might also involve first solving the "deref" problem, as well as the conditionally-copyable problem.
I like this, but given that this can't be possible:
struct S {
indirect var prop: S
}
without prop being optional †, maybe it's just a matter of proving IndirectOptional in addition to normal and unsafely unwrapped optionals?
struct S {
var prop: IndirectOptional<S>
}
Along with the superpowers of optionals (auto promoting values to .some(), syntax sugar around ? and ! on the use sites, etc).
† OTOH, we may treat indirect var prop: S as a synonym for something like var prop: IndirectOptional<S>. But will that be obvious?
I think this would be a great feature on its own. I completely agree that having all the capabilities of Optional in IndirectOptional is valuable.
That being said, a UniqueHeapReference is also useful in scenarios where the value stored on the heap doesn't need to be optional. I assume IndirectOptional could be a separate proposal, and if so, I fully support it.
Hi folks, I’ve updated the proposal again to rename this type to Unique which adds a little clarity that this construct uniquely holds its value unlike the common Box type in Swift which is typically implemented with a class (which is more like a theoretical Shared type). You can find the updated text here: Box by Azoy · Pull Request #3067 · swiftlang/swift-evolution · GitHub I also added an alternative considered for the old Box name.
I was thinking that there might not have been a lot of precedent for a Standard Library types not named as a noun. Box is a noun… it's a thing. Unique is an adjective… or potentially a verb in some situations.
But then I remembered SE-0410 and Atomic which is documented as: An atomic value. So it does look a little unorthodox to not name this type with a noun… but not without precedent.
One potential "internal" ripple effect might be all the existing "make unique" and "ensure unique" functions scattered around. There might be some small chance that an engineer looking at that API would be confused that does not really map to a Unique type. But that's probably more of an internal Swift discussion that can take place because I do not see very many public examples of those.
I do still think we want interop with UnsafePointer—more than just Span, in that we should promise that the address is stable. I expect this to be the type that will let people stop using UnsafeMutablePointer.allocate, but still allow them to pass addresses to C APIs [EDIT: that need to persist them]—especially when they learn inout-to-pointer conversion is only stable in a few rare cases.
I don’t know. Int vs Atomic<Int>, pretty clear what’s different, and you can read the second one as “atomic int”, in contrast with “array of int” and alongside “optional int / optional of int” that people say both ways. Int vs Unique<Int>? What makes the Unique one Unique? If we use the more nouny reading, a unique what of T? It’s going to have to be explained in the documentation even if it doesn’t end up in the name of the type, so it’s not like we don’t know what noun to put in there.
The thing that’s unique in C++’s unique_ptr isn’t quite the “ptr”, but at least it isn’t claiming the payload is “unique”. (I might try something like “a pointer with unique ownership”, but unlike the proposal here unique_ptr definitely exposes its pointer-ness.)
UniqueBox isn’t bad. It may not be The Optimal Name, but I expect this type will be rarer than it is in Rust, since the “default” on-heap thing in Swift will remain a class. I think OwningPointer / OwnedPointer is still my own personal favorite, which, well, I did suggest it already. It may not make that much sense without exposing interop with UnsafePointer, but as argued above I think we want that.
I'm sure it's just a lack of imagination on my part, and I'm not trying to be contrarian, but if Box/Unique doesn't guarantee a stable address… what does it do?
It puts something on the heap, sure… but in a way that cannot be usefully observed by a caller, it seems? What then can I do with Box<T>/Unique<T> that I can't do with just T?