What are the dangers of making my `Array` type a `class` instead of a `struct` in our custom standard library?

(This question kind of straddles the standard library and parts of the compiler, I wasn't sure which sub-forum to post it on.)

In our specialised (bare metal, very low memory use, no objective c support) stdlib, our Array type uses non standard internals and semantics.

Instead of using _ContiguousArrayBuffer (and thus _ContiguousArrayStorage) as the internal buffer, I use a simple struct (let's call it something like SimpleArrayBuffer) that contains and controls an UnsafeMutableBufferPointer. My arrays are fixed size (defined at initialisation), any mutation to add or remove elements is forbidden (element replacement is allowed if the array instance is mutable).

Also, the arrays do not follow value type semantics with the internal C.O.W. mechanism used in "standard" Swift. This is all intended and I don't plan to change any of it. (It might be controversial to some but it suits our platform.)

When I wrote the original implementation, ARC was not functional in our runtime and classes did not work. We now have a simple implementation of ARC in our runtime, that is suitable for supporting classes, closures, etc. on our constrained devices.

Due to a lack of ARC, although the original Array type in our standard library was effectively a heap allocated reference type, it had no automatic memory management, and users of the language had to remember to call a .deallocate() method on the Array in order to free up heap space when the collection was no longer needed. I want to improve this so that our arrays are automatically memory managed like any other class.

The simplest way seems to be to change this...

@frozen
public struct Array<Element>: _DestructorSafeContainer

...to...

@frozen
public class Array<Element>: _DestructorSafeContainer

But Array is not a normal type in the standard library. It's highly tied to the compiler and optimiser.

So my questions are...

  1. are there any "dangers" to this approach, where I mean something like "the compiler will produce invalid code or the compiler will probably crash because it is designed to assume Array is a value type/struct, not a class type?"

And (to a lesser extent)...

  1. "certain SIL optimisations are so fundamentally tied to the value typed nature of Array that they will perform very badly or crash the compiler in many common situations."

I don't mind doing small patches to our (slightly customised) compiler, but if
the above change is fundamentally flawed, it will be easier to add in some middle layers with class types to do the work for me.

Thanks for any advice and help that you guys can give!

Regards,
Carl

1 Like

I know you say you’re committed to using reference semantics, but that seems like a poor idea. It sounds like what you you really want is for Array to be a move-only type, maybe with some explicit clone() operation. That’s certainly what we’ve thought about for highly-constrained environments in the past.

I think all of the optimization assumptions in the compiler about Array are communicated by semantics attributes rather than hard-coded.

3 Likes

To me this reads like you should choose a different name for the type altogether. Using the name Array for something that differs so much in semantics sounds like it would do more harm than good by evoking false expectations in users and allowing existing source to compile but behave wildly differently.

3 Likes

Many languages (rust, java, C#, python) use reference semantics for container types; across industry it is rather a norm than an exception, so what you are doing is definitely possible. I'd use a different name, like RefArray to make it less confusing.

A move-only array type would still be required to be a class or have class-based storage for automatic memory management, right?

Move only types aren't implementable in Swift yet, but when they are added they should be able to be structs.

My question is, wouldn’t that also require deinitializers on structs, or would there be a different mechanism for automatic memory management for move-only types?

1 Like

IIRC deinitializers were the plan for move only structs.

2 Likes

Yes, it would make sense for a move-only struct to be able to have a deinit.

5 Likes