+1 On Friday, 18 March 2016, Jordan Rose via swift-evolution < swift-evolution@swift.org> wrote:
> Hey, everyone. If you're like me, you're sick of the fact that > 'UnsafePointer<Int>' doesn't tell you whether or not the pointer can be > nil. Why do we need to suffer this indignity when reference types—including > function pointers!—can distinguish "present" from "absent" with the > standard type 'Optional'? Well, good news: here's a proposal to make > pointer nullability explicit. 'UnsafePointer<Int>?' can be null (nil), > while 'UnsafePointer<Int>' cannot. Read on for details! > > > https://github.com/jrose-apple/swift-evolution/blob/optional-pointers/proposals/nnnn-optional-pointers.md > > Bonus good news: I've implemented this locally and updated nearly all the > tests already. Assuming this is accepting, the actual changes will go > through review as a PR on GitHub, although it's mostly going to be one big > mega-patch because the core change has a huge ripple effect. > > Jordan > > --- > > Make pointer nullability explicit using Optional > > - Proposal: SE-NNNN > > <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md> > - Author(s): Jordan Rose <https://github.com/jrose-apple> > - Status: *Awaiting review* > - Review manager: TBD > > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#introduction> > Introduction > > In Objective-C, pointers (whether to objects or to a non-object type) can > be marked as nullable or nonnull, depending on whether the pointer value > can ever be null. In Swift, however, there is no such way to make this > distinction for pointers to non-object types: an UnsafePointer<Int> might > be null, or it might never be. > > We already have a way to describe this: Optionals. This proposal makes > UnsafePointer<Int> represent a non-nullable pointer, and > UnsafePointer<Int>? a nullable pointer. This also allows us to preserve > information about pointer nullability available in header files for > imported C and Objective-C APIs. > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#motivation> > Motivation > > Today, UnsafePointer and friends suffer from a problem inherited from C: > every pointer value could potentially be null, and code that works with > pointers may or may not expect this. Failing to take the null pointer case > into account can lead to assertion failures or crashes. For example, pretty > much every operation on UnsafePointer itself requires a valid pointer > (reading, writing, and initializing the pointee or performing arithmetic > operations). > > Fortunately, when a type has a single invalid value for which no > operations are valid, Swift already has a solution: Optionals. Applying > this to pointer types makes things very clear: if the type is non-optional, > the pointer will never be null, and if it *is*optional, the developer > must take the "null pointer" case into account. This clarity has already > been appreciated in Apple's Objective-C headers, which include nullability > annotations for all pointer types (not just object pointers). > > This change also allows developers working with pointers to take advantage > of the many syntactic conveniences already built around optionals. For > example, the standard library currently has a helper method on > UnsafeMutablePointer called _setIfNonNil; with "optional pointers" this > can be written simply and clearly: > > ptr?.pointee = newValue > > Finally, this change also reduces the number of types that conform to > NilLiteralConvertible, a source of confusion for newcomers who (reasonably) > associate nil directly with optionals. Currently the standard library > includes the following NilLiteralConvertible types: > > - Optional > - ImplicitlyUnwrappedOptional (subject of a separate proposal by Chris > Willmore) > - _OptionalNilComparisonType (used for optionalValue == nil) > - *UnsafePointer* > - *UnsafeMutablePointer* > - *AutoreleasingUnsafeMutablePointer* > - *OpaquePointer* > > plus these Objective-C-specific types: > > - *Selector* > - *NSZone* (only used to pass nil in Swift) > > All of the italicized types would drop their conformance to > NilLiteralConvertible; the "null pointer" would be represented by a nil > optional of a particular type. > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#proposed-solution>Proposed > solution > > 1. > > Have the compiler assume that all values with pointer type (the > italicized types listed above) are non-null. This allows the representation > of Optional.none for a pointer type to be a null pointer value. > 2. > > Drop NilLiteralConvertible conformance for all pointer types. > 3. > > Teach the Clang importer to treat _Nullable pointers as Optional (and > _Null_unspecified pointers as ImplicitlyUnwrappedOptional). > 4. > > Deal with the fallout, i.e. adjust the compiler and the standard > library to handle this new behavior. > 5. > > Test migration and improve the migrator as necessary. > > This proposal does not include the removal of the NilLiteralConvertible > protocol altogether; besides still having two distinct optional types, > we've seen people wanting to use nil for their own types (e.g. JSON > values). (Changing this in the future is not out of the question; it's just > out of scope for this proposal.) > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#detailed-design>Detailed > design > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#api-changes>API > Changes > > - > > Conformance to NilLiteralConvertible is removed from all types except > Optional, ImplicitlyUnwrappedOptional, and _OptionalNilComparisonType, > along with the implementation of init(nilLiteral:). > - > > init(bitPattern: Int) and init(bitPattern: UInt) on all pointer types > become failable; if the bit pattern represents a null pointer, nil is > returned. > - > > Process.unsafeArgv is a pointer to a null-terminated C array of C > strings, so its type changes from > UnsafeMutablePointer<UnsafeMutablePointer<Int8>> to > UnsafeMutablePointer<UnsafeMutablePointer<Int8>?>, i.e. the inner > pointer type becomes optional. It is then an error to access > Process.unsafeArgv before entering main. (Previously you would get a > null pointer value.) > - > > NSErrorPointer becomes optional: > > -public typealias NSErrorPointer = > AutoreleasingUnsafeMutablePointer<NSError?>+public typealias NSErrorPointer = > AutoreleasingUnsafeMutablePointer<NSError?>? > > > - A number of methods on String that came from NSString now have > optional parameters: > > public func completePathIntoString(- outputName: > UnsafeMutablePointer<String> = nil,+ outputName: > UnsafeMutablePointer<String>? = nil, > caseSensitive: Bool,- matchesIntoArray: > UnsafeMutablePointer<[String]> = nil,+ matchesIntoArray: > UnsafeMutablePointer<[String]>? = nil, > filterTypes: [String]? = nil > ) -> Int { > > public init( > contentsOfFile path: String,- usedEncoding: > UnsafeMutablePointer<NSStringEncoding> = nil+ usedEncoding: > UnsafeMutablePointer<NSStringEncoding>? = nil > ) throws { > > public init( > contentsOfURL url: NSURL,- usedEncoding enc: > UnsafeMutablePointer<NSStringEncoding> = nil+ usedEncoding enc: > UnsafeMutablePointer<NSStringEncoding>? = nil > ) throws { > > public func linguisticTags( > in range: Range<Index>, > scheme tagScheme: String, > options opts: NSLinguisticTaggerOptions = [], > orthography: NSOrthography? = nil,- tokenRanges: > UnsafeMutablePointer<[Range<Index>]> = nil+ tokenRanges: > UnsafeMutablePointer<[Range<Index>]>? = nil > ) -> [String] { > > > - > > NSZone's no-argument initializer is gone. (It probably should have > been removed already as part of the Swift 3 naming cleanup.) > - > > A small regression: optional pointers can no longer be passed using > withVaList because it would require a conditional conformance to the > CVarArg protocol. For now, using unsafeBitCast to reinterpret the > optional pointer as an Int is the best alternative; Int has the same C > variadic calling conventions as a pointer on all supported platforms. > > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#conversion-between-pointers>Conversion > between pointers > > Currently each pointer type has initializers of this form: > > init<OtherPointee>(_ otherPointer: UnsafePointer<OtherPointee>) > > This simply makes a pointer with a different type but the same address as > otherPointer. However, in making pointer nullability explicit, this now > only converts non-nil pointers to non-nil pointers. In my experiments, this > has led to this idiom becoming very common: > > // Before:let untypedPointer = UnsafePointer<Void>(ptr) > // After:let untypedPointer = ptr.map(UnsafePointer<Void>.init) > // Usually the pointee type is actually inferred: > foo(ptr.map(UnsafePointer.init)) > > I consider this a bit more difficult to understand than the original code, > at least at a glance. We should therefore add new initializers of the > following form: > > init?<OtherPointee>(_ otherPointer: UnsafePointer<OtherPointee>?) { > guard let nonnullPointer = otherPointer else { > return nil > } > self.init(nonnullPointer) > } > > The body is for explanation purposes only; we'll make sure the actual > implementation does not require an extra comparison. > > (This would need to be an overload rather than replacing the previous > initializer because the "non-null-ness" should be preserved through the > type conversion.) > > The alternative is to leave this initializer out, and require the nil case > to be explicitly handled or mapped away. > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#open-issue-unsafebufferpointer>Open > Issue: UnsafeBufferPointer > > The type UnsafeBufferPointer represents a bounded typed memory region > with no ownership or lifetime semantics; it is logically a bare typed > pointer (its baseAddress) and a length (count). For a buffer with 0 > elements, however, there's no need to provide the address of allocated > memory, since it can't be read from. Previously this case would be > represented as a nil base address and a count of 0. > > With optional pointers, this now imposes a cost on clients that want to > access the base address: they need to consider the nil case explicitly, > where previously they wouldn't have had to. There are several possibilities > here, each with their own possible implementations: > > 1. > > Like UnsafePointer, UnsafeBufferPointer should always have a valid > base address, even when the count is 0. An UnsafeBufferPointer with a > potentially-nil base address should be optional. > 1. > > UnsafeBufferPointer's initializer accepts an optional pointer and > becomes failable, returning nil if the input pointer is nil. > 2. > > UnsafeBufferPointer's initializer accepts an optional pointer and > synthesizes a non-null aligned pointer value if given nil as a base > address. > 3. > > UnsafeBufferPointer's initializer only accepts non-optional > pointers. Clients such as withUnsafeBufferPointermust synthesize a > non-null aligned pointer value if they do not have a valid pointer to > provide. > 4. > > UnsafeBufferPointer's initializer only accepts non-optional > pointers. Clients *using* withUnsafeBufferPointermust handle a nil > buffer. > 2. > > UnsafeBufferPointer should allow nil base addresses, i.e. the > baseAddress property will be optional. Clients will need to handle > this case explicitly. > 1. > > UnsafeBufferPointer's initializer accepts an optional pointer, but > no other changes are made. > 2. > > UnsafeBufferPointer's initializer accepts an optional pointer. > Additionally, any buffers initialized with a count of 0 will be > canonicalized to having a base address of nil. > > I'm currently leaning towards option (2i). Clients that expect a pointer > and length probably shouldn't require the pointer to be non-null, but if > they do then perhaps there's a reason for it. It's also the least work. > Chris (Lattner) is leaning towards option (1ii), which treats > UnsafeBufferPointer similar to UnsafePointer while not penalizing the > common case of withUnsafeBufferPointer. > > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#impact-on-existing-code>Impact > on existing code > > Any code that uses a pointer type (including Selector or NSZone) may be > affected by this change. For the most part our existing logic to handle > last year's nullability audit should cover this, but the implementer should > test migration of several projects to see what issues might arise. > > Anecdotally, in migrating the standard library to use this new logic I've > been quite happy with nullability being made explicit. There are many > places where a pointer really *can't* be nil. > > > <https://github.com/jrose-apple/swift-evolution/tree/optional-pointers#alternatives-considered>Alternatives > considered > The primary alternative here would be to leave everything as it is today, > with UnsafePointer and friends including the null pointer as one of their > normal values. This has obviously worked just fine for nearly two years of > Swift, but it is leaving information on the table that can help avoid bugs, > and is strange in a language that makes fluent use of Optional. As a fairly > major source-breaking change, it is also something that we probably should > do sooner rather than later in the language's evolution. > -- -- Howard.
_______________________________________________ swift-evolution mailing list swift-evolution@swift.org https://lists.swift.org/mailman/listinfo/swift-evolution