Noncopyable Generics in Swift: A Code Walkthrough

I suspect the answer is no. I've been playing about with Ben's example to build up a lock-free queue, and the reality is that you don't actually need the atomic to support a non-copyable type: you need it to support a pointer to one. The support here ought to be sufficient.

3 Likes

is it possible to create struct Vector<Elem: ~Copyable>: ~Copyable, Sequence ?
Does it make sense to conform to both ~Copyable and Sequence ?

@lukasa, Could you explain more, bc that seems... surprising. Intuitively, I would have thought that pointing at a move-only type was defeating the purpose of having it to begin with.

Does it make sense to conform to both ~Copyable and Sequence?

struct Vector<Elem: ~Copyable>: ~Copyable is not a "conformance to ~Copyable". There is no such thing as a ~Copyable protocol to conform to. The constraint says "this struct does not unconditionally conform to Copyable" or even more precisely "the (normally defaulted) Copyable conformance has been suppressed". So the full reading of this is something like "Vector is a struct, generic on a type Elem which cannot be assumed to be Copyable. Vector does not unconditionally conform to Copyable".

Sequence (like all protocols that predate Swift 6.0) refines Copyable, so this does not make sense. Conformance to Sequence requires that Vector be Copyable, and so it must not have its conformance suppressed.

We (the Swift community broadly, and Apple's compiler and standard library teams specifically) are investigating how best to provide collection-like protocols that support types without the Copyable (or Escapable) constraints, but this is more complex than designing the existing (Copyable) collection protocols was, and it's only just recently become possible for people to experiment with nearly-stable tools, so it will take a little bit of experience to figure out what the best patterns are.

10 Likes

The insight here is that these two constructions are very similar:

struct Foo: ~Copyable { }

class Bar {
    var f: Foo
}

let b1 = UnsafeMutablePointer<Foo>.allocate(capacity: 1).initialize(to: Foo())
let b2 = Bar(f: Foo())

In both of these cases there is a heap allocated box that stores a Foo. In both cases we have a reference to that box: for b1, the reference is the pointer, for b2, the reference is the class.

Importantly, nothing prevents you copying the reference: you can have as many copies of that as you want. You can also borrow that reference all you want: accessing the noncopyable type through its memory location is sound.

The non-copyable type defenses prevent you doing the thing you aren't allowed to do, which is to copy the type out of the box. This defense works in both cases.

2 Likes

Sequence (like all protocols that predate Swift 6.0) refines Copyable, so this does not make sense. Conformance to Sequence requires that Vector be Copyable, and so it must not have its conformance suppressed.

According to docs:

The Sequence protocol makes no requirement on conforming types regarding whether they will be destructively consumed by iteration.
(Sequence | Apple Developer Documentation)

How to understand it?

It would be possible to retroactively change Sequence to be marked ~Copyable and still fulfill the letter of this, yes. makeIterator() is already marked __consuming (the precursor to consuming used by the standard library). Iteration of a non-copyable sequence would therefore consume it.

The trouble is, this would be very annoying for a lot of cases. People assume that you can iterate a sequence more than once, non-destructively, and indeed you can in most cases. Like our list example – the whole point of forEach instead of pop in the example above is that you can do it non-destructively.

More design is needed here. Probably alongside further language features, like the ability to have borrowed references to elements. As it happens, the way sequences and collections work is sub-optimal for a bunch of other reasons (including being vague on the question of multi-pass), and that should probably be tackled together with this question.

10 Likes

However it’s worth pointing out we cannot make the Element associated type be ~Copyable without a source break.

8 Likes