But Span based API is said to be a safer alternative to what we used before (such as unsafe pointers, which become doubly unsafe when leaked from closure-based APIs like withUnsafePointer). Are Spans only safe under certain circumstances - specifically when the compiler can statically guarantee their safety - and unsafe otherwise? If so, how can I tell whether the compiler has actually verified their safety, ideally without having to inspect the generated code? This doesn’t sound entirely "safe" to me.
The lifetime of the Span is bound to the lifetime of the object it's spanning over. Unless you convert the span to an unsafe (buffer) pointer, the compiler won't let you use the span in a way that could allow unsafely. The entire point of having spans is that the compiler can always statically verify their safety. All of the safety rules that the withUnsafeBlahBlahBlah methods exist to enforce are just built into the type system for spans.
I simplified it a bit. The issue is specific to classes. Struct fails as expected.
class C {
var array = [1, 2, 3, 4, 5]
func foo() {
let span = array.span
array.removeAll() // This compiles
print(span[1])
}
}
Likewise.
Way back in the day there was an effort to fix this, but bad timing meant that it never landed. See Revisiting SE-0132 Rationalizing Sequence end-operation names for more of the backstory.
I believe there are plans to revisit the collection types for other reasons (primarily to allow for non-copyable types) and I’d love to see this get tidied up at the same time. Still, that’s firmly in the realm of Swift Evolution, which isn’t really my bailiwick (but it could be yours :-).
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
Shouldn't compiler fail to compile this whenever it can't proof safety?
(even if nothing wrong is actually happening). For example:
@MainActor protocol P: AnyObject { func doSomethingQuestionable() }
@MainActor class C {
static let singleton: C = C()
private init() {}
weak var delegate: P?
var array = [1, 2, 3, 4, 5]
func foo() {
let span = array.span
print("got span")
// compiler has no visibility about what's going to happen
delegate?.doSomethingQuestionable()
print("use span", span[1])
}
}
// elsewhere:
let test = Test()
C.singleton.delegate = test
C.singleton.foo()
@MainActor class Test: P {
func doSomethingQuestionable() {
print("ruin span")
C.singleton.array.removeAll()
}
}