To help keep proposals moving forward, the Swift core team has set aside some time specifically for design discussions of upcoming proposals. Below are some rough notes from the yesterday's discussion.
(This week, I want to point out that my notes for PR 219, the first discussion topic, are especially rough.)
These are informal comments, intended to guide the proposals in directions that draw constructive feedback. You are welcome to ignore the feedback, agree with it, or disagree with it. As always, the formal decision doesn't happen until after the review period ends.
Make pointer nullability explicit using Optional <file:///Users/alexmartini/DevPubs%20Git%20Repositories/Swift%20Language%20Review/_build/html/LR_MeetingNotes/2016-03-23.html#make-pointer-nullability-explicit-using-optional>
Biggest open issue is what to do with UnsafeBufferPointer which has a base address and a count of the number of elements at that address. The most common use is to do fast things with an array. The problem is when you have an empty array.
We have a statically initialized empty array, so this doesn’t apply to array. But slices and Cocoa arrays can do it.
Half of the use cases are subscripting off of the buffer, so they don’t actually use the base address. They can’t actually subscript an empty array, but it’s not a syntax error — the loop is run zero times, so it doesn’t matter. The other half pass the pointers down to a C API that takes an address and count.
Someone might expect that the base address doesn’t change when something is initialized.
We can’t easily use the zero pointer because SIL already uses it for nil. But there are issues with using the same representation as C to avoid bridging costs.
We’re mapping two things in C onto one thing in Swift. In C, the buffer pointer would be __nullable long * and the length is ulong.
Given everything else in the system, it’s more like pointer. We didn’t call it a buffer because that tends to imply ownership.
Sketching out the state space:
Pointer Length Static type
null 0 UBP?
valid >= 0 UBP
valid < 0 X
vull != 0 ???
This issue would go away if we got rid of the base address on UnsafeBufferPointer, but that would get rid of a number of valid C operations like calling memcopy.
It seems like withUnsafeBufferPointer should never produce nil. With that in mind, why should UnsafeBufferPointer need to?
We do need a properly-aligned “valid” invalid pointer. LLVM makes assumptions about things being aligned.
Dominant feedback on the list has been for people want something that round trips cleanly. Making the base address non-optional adds overhead and removes the ability to round trip.
It’s unfortunate that we don’t have a way to represent in the type system a buffer pointer that isn’t nullable, from within withUnsafeBufferPointer which wouldn’t even call its closure if the buffer has a null base address.
Allow Swift types to provide custom Objective-C representations <file:///Users/alexmartini/DevPubs%20Git%20Repositories/Swift%20Language%20Review/_build/html/LR_MeetingNotes/2016-03-23.html#allow-swift-types-to-provide-custom-objective-c-representations>
The associated type could be AnyObject rather than NSObject. The use case for a non-subclass of NSObject is very narrow, but it’s not a needed restriction.
The unconditionalyBridgeFromObjectiveC function can probably go away. Calling initializers from the downcasting infrastructure is horrible. If we need a function, they
This doesn’t break the ability of the optimizer to reason about what a dynamic cast can do. We require that the bridgeable conformance must be in the same module as where the type is defined, and we have a white list of things that don’t follow that. Ok... but do we want people to expand casting this way? If we say no, we should take it away from array and string and dictionary too.
You shouldn’t need implicit conversions — the use case is very narrow, and we would rather have things use explicit conversions. The APIs come in with the right type; the implementation of the bridged type has to do conversion, but its clients don’t have to see that. From the Swift point of view, there won’t be any APIs that take the reference type.
Implicit conversions. In this proposals, you don’t get implicit conversions. Have a separate discussion about whether we can get rid of the four types that have implicit conversion. We see the existing ones as deeply problematic.
Dynamic casts. For example, AnyObject to a bridged value type. The whole reason for the dynamic cast infrastructure is to make the reference types irrelevant. Should this be using cast syntax or should we have a different type of function? It’s hard to describe what as does. It’s magic because you are casting AnyObject to a struct — calling it AnyObject doesn’t make a lot of sense.
If we have reference counted existentials, we could merge Any and AnyObject.
Resilience concern: you can not add this protocol after the type has been published.
SE-0054: Abolish ImplicitlyUnwrappedOptional type <file:///Users/alexmartini/DevPubs%20Git%20Repositories/Swift%20Language%20Review/_build/html/LR_MeetingNotes/2016-03-23.html#se-0054-abolish-implicitlyunwrappedoptional-type>
IUO as a type is bad; it should become conceptually a decl attribute instead. If we import things that can be nil, their formal type is Optional but we can add an attribute that says when you form a rvalue to that thing, you get implicit unwrapping. The typechecker inserts a diamond that lets you convert it to T? or T. Meaning T! never enters the type system — although it does still appear in user-facing decl descriptions.
For example:
let y = P // y is of type T?
let y: T! = P // y is of type T!
let x = [P] // x is of type [T?]
One issue is that we don’t have a good way to mark a function that takes a block pointer, when that block has not been audited.
void foo(C *(*fp)(C* x)) {}
func foo (fp: ((C?) -> C?)!) {}
func foo (@IUO fp: (C?) -> C?) {}
That is a regression from our current behavior. It would have to be a type attribute because they can chain. This will show up in places where you return (and then call) a block.
For example, you can no longer express the type let y: (T?, U!).
We don’t need to have a diamond for currying, only for rvalue access and application.
x.foo() // T!
Type.foo(x)() // T? return type
A source breaking change here is that if extract something into an intermediate variable could change type in a very important way. That’s already somewhat true because of overload resolution, but this makes it much more visible.
Does this gloss over the audit problem? We’ve gotten most of the really important stuff audited, but there are lots of unaudited things such as most of the SDK on Linux. This means we don’t propagate the IUO stuff up, meaning we end up with more explicit forces in code when you are using unaudited API.
Deferred initialization is still a good argument for why we need the feature. This approach locks down propagation and makes IUO more predictable: if the expression can typecheck as optional it will, otherwise it will force.