Pitch: Improved Swift pointers

I am not very familiar with the inner workings of the standard library,
however I maintain a few libraries which make extensive use of Swift
pointers, such as GitHub - tayloraswift/swift-png: decode, inspect, edit, and encode png images in pure swift which makes
extensive use of Unsafe_____Pointers. I also write a lot of code that
interfaces with C APIs like Cairo and OpenGL. Most of the ideas in the
original proposal came from me dealing with the current Swift pointer APIs
in my own code. For example I find myself writing this bit of code

let buffer = UnsafeMutableBufferPointer<UInt8>(start: UnsafeMutablePointer<UInt8>.allocate(capacity: byteCount), count: byteCount)defer
{
    buffer.baseAddress?.deallocate(capacity: buffer.count)
}

far more than I would like to. While this proposal doesn’t solve every problem with Swift pointers — for example, we need a UMBP initializer that takes an immutable buffer pointer before we are really able to write a lot of examples more concisely, it takes us a great deal closer to being able to write things like

UnsafeMutablePointer(mutating: self.zero_line.baseAddress!).deallocate(capacity: self.zero_line.count)

as

UnsafeMutableBufferPointer(mutating: self.zero_line).deallocate()

You should not need to do this at all. A pointer does not need to be
mutable to deallocate the memory. That’s a bug in the UnsafePointer API.

-Andy

I think this falls under “convenience methods that would be really nice to
have but should probably go in a separate proposal”.

···

On Fri, Jul 14, 2017 at 12:29 AM, Andrew Trick <atrick@apple.com> wrote:

On Jul 13, 2017, at 6:16 PM, Taylor Swift via swift-evolution < > swift-evolution@swift.org> wrote:

On Thu, Jul 13, 2017 at 2:47 PM, Dave Abrahams via swift-evolution < > swift-evolution@swift.org> wrote:

on Wed Jul 12 2017, Taylor Swift <swift-evolution@swift.org> wrote:

> Hi all, I’ve written up a proposal to modify the unsafe pointer API for
> greater consistency, safety, and ease of use.
>
> ~~~
>
> Swift currently offers two sets of pointer types — singular pointers
such
> as UnsafeMutablePointer, and vector (buffer) pointers such as
UnsafeMutable
> *Buffer*Pointer. This implies a natural separation of tasks the two
kinds
> of pointers are meant to do. For example, buffer pointers implement
> Collection conformance, while singular pointers do not.
>
> However, some aspects of the pointer design contradict these implied
roles.
> It is possible to allocate an arbitrary number of instances from a type
> method on a singular pointer, but not from a buffer pointer. The result
of
> such an operation returns a singular pointer, even though a buffer
pointer
> would be more appropriate to capture the information about the *number*
of
> instances allocated. It’s possible to subscript into a singular pointer,
> even though they are not real Collections. Some parts of the current
design
> turn UnsafePointers into downright *Dangerous*Pointers, leading users to
> believe that they have allocated or freed memory when in fact, they have
> not.
>
> This proposal seeks to iron out these inconsistencies, and offer a more
> convenient, more sensible, and less bug-prone API for Swift pointers.
>
> <https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt;

I have no problem with this direction in principle; it sounds like a
good idea. However, before affirming any particular design I would like
to see the results of any such proposal applied to a fairly large body
of code that uses Unsafe[XXX]Pointer today in diverse ways (such as the
Swift standard library itself), to demonstrate that it does in fact
improve the code in practice.

Cheers,

--
-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

If my memory is correct, Swift’s `deallocate` requires a pointer that was allocated via Swift’s `allocate`.

What’s missing from the docs I suppose is the fact that we now have a set of Swift allocate/deallocate entry points, which are of course compatible as long as the user keeps track of the capacity and alignment. IMO the buffer pointer API should alleviate that responsibility from the user.

-Andy

···

On Jul 14, 2017, at 11:27 AM, Michael Ilseman <milseman@apple.com> wrote:

I also have to say it’s not common to deallocate something in Swift that you didn’t previously allocate in Swift.

I’m not sure it is even defined to use `deallocate` on memory that wasn’t `allocated` in Swift. Andy, thoughts here? Perhaps more clarity in deallocate’s documentation is needed?

How would you feel about:

struct UnsafeMutableRawBufferPointer
{

--- static func allocate(count:Int) -> UnsafeMutableRawBufferPointer
+++ static func allocate(bytes:Int, alignedTo:Int) ->
UnsafeMutableRawBufferPointer
    func deallocate()
+++ func bindMemory<Element>(to:Element.Type, capacity:Int)
+++ func copy(from:UnsafeRawBufferPointer, bytes:Int)
+++ func initializeMemory<Element>(as:Element.Type, at:Int, count:Int,
+++ func initializeMemory<Element>(as:Element.Type,
+++ func moveInitializeMemory<Element>(as:Element.Type,
}

“bytes” = 8 bit quantities (don’t @ me we’re assuming 8 bit bytes)
“capacity” = strided quantities, not assumed to be initialized
“count” = strided quantities, assumed to be initialized

It’s also worth nothing that a lot of what the proposal tries to add to
UnsafeBufferPointer is already present in UnsafeMutableRawPointer like a
sizeless deallocate() and a sizeless copyBytes(from:).

Although I’m not sure what’s going on with the latter one
<https://developer.apple.com/documentation/swift/unsafemutablerawbufferpointer/2635415-copybytes&gt;…lol
swiftdoc

···

to:Element)
from:UnsafeBufferPointer<Element>, count:Int)
from:UnsafeMutableBufferPointer<Element>, count:Int

On Fri, Jul 14, 2017 at 1:57 AM, Andrew Trick <atrick@apple.com> wrote:

On Jul 13, 2017, at 10:30 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I’m confused I thought we were talking about the naming choices for the
argument labels in those functions. I think defining and abiding by
consistent meanings for `count`, `capacity`, and `bytes` is a good idea,
and it’s part of what this proposal tries to accomplish. Right now half the
time we use `count` to refer to “bytes” and half the time we use it to
refer to “instances”. The same goes for the word “capacity”. This is all
laid out in the document:

“““
*Finally, the naming and design of some UnsafeMutableRawPointer members
deserves to be looked at. The usage of capacity, bytes, and count as
argument labels is wildly inconsistent and confusing.
In copyBytes(from:count:), count refers to the number of bytes, while
in initializeMemory<T>(as:at:count:to:) and initializeMemory<T>(as:from:count:), count refers
to the number of strides.
Meanwhile bindMemory<T>(to:capacity:) uses capacity to refer to this
quantity. The always-problematic deallocate(bytes:alignedTo) method
and allocate(bytes:alignedTo:) type methods use bytes to refer to
byte-quantities. Adding to the
confusion, UnsafeMutableRawBufferPointer offers an allocate(count:) type
method (the same signature method we’re trying to add
to UnsafeMutableBufferPointer), except the count in this method refers to
bytes. This kind of API naming begets stride bugs and makes Swift
needlessly difficult to learn.*
”””

The only convenience methods this proposal is trying to add is the
functionality on the buffer pointer types. There seems to be broad support
for adding this functionality as no one has really opposed that part of the
proposal yet. Any other new methods like `UnsafeMutablePointer.assign(to:)`
are there for API consistency.

This proposal also calls for getting rid of one of those “redundant
initializers” :)

Since we’re not bike-shedding the specifics yet, I’ll just give you some
background.

We would ultimately like APIs that allocate and initialize in one go. It’s
important that the current lower-level (dangerous) APIs make a clear
distinction between initialized and uninitialized memory to avoid confusing
them with future (safer) APIs. `capacity` always refers to memory that may
be uninitialized. I think that’s very clear and helpful.

In the context of pointers `count` should always be in strides. For raw
pointers, that happens to be the same as as `bytes`.

I initially proposed copy(bytes:from:), but someone thought that `bytes`
in this particular context did not properly convey the "count of bytes" as
opposed to the source of the bytes. You’re right, that’s inconsistent with
allocate/deallocate(bytes:), because allocateBytes(count:) would be silly.
Just be aware that the inconsistency is a result of over-thinking and
excessive bike shedding to the detriment of something that looks nice and
is easy to remember.

I should also point out that the inconsistencies in functionality across
pointer types, in terms of collection support and other convenience, is
also known but was deliberately stripped from proposals as “additive”.

-Andy

For the sake of argument, I’m gonna claim that instead,
UnsafeMutableBufferPointer is the low level necessity, and
UnsafeMutablePointer is the low level convenience structure.

Suppose that every block of memory has a starting point, and a length. An
UnsafeBufferPointer represents that, since all an UnsafeBufferPointer is,
is a start address, and a length value, bundled together into one variable
instead of two. Then we can say that UnsafePointer is a *special case* of
UnsafeBufferPointer, where the length value is *unknown*.

Mind you I didn’t say that the length value *equals* *1*. But it *might as
well* be. Because when the length is unknown,* the only index where you can
reasonably expect there to be a valid value is index zero*. You wouldn’t
feel confident trying to do something to the memory address 3 strides down
from the pointer. If you did feel confident doing that, well now you have a
length value. The length value is 4. Your regular Pointer is now a
BufferPointer.

The only reason any of this matters, is because all of our memory and
memorystate -related functions take size arguments. You have to fill in the
size argument with *something*. That *something* is the buffer `count`.
Even if you don’t actually know the number for the `count`, you still have
to supply some value, in which case the “only” sensible choice is “1”.
That’s why you could argue that UnsafePointer is just a special case of a
buffer pointer where `count` is 1. The UnsafePointer API demands
information that only BufferPointers know. You could design an API where
buffer pointers are the only pointers that exist, and everything would
still work fine. Just because they have high-level *capabilities* doesn’t
mean they can’t do everything plain pointers can do just as efficiently. A
plain pointer takes up one word of storage, and you use another word of
storage yourself to track the size. A buffer pointer stores the two words
next to each other. You could ignore the size word and track the size
yourself, taking up three words of storage, but that would just be silly.

*But wait! I don’t need to track the size*, you say! *The pointer just
points to one item! I just need the address word, not the size word!* Well,
*yeah*. That’s why I proposed a sizeless singular pointer API, so you don’t
have to go around plugging “1”s everywhere and save yourself a word of
storage. You can argue that’s the *actual* high-level API, since it
abstracts away the length stuff. But if “1” isn’t your answer to the
question “on how many instances should this method operate on”, go back to
paragraph 3 and convince yourself that what you really have is an buffer
pointer, not a plain pointer.

Now I thought of an exception to this, like when you’re assigning 8-bit
RGBA values to an image buffer and it might make sense to write something
like this in the current API:

let size:Int = height * width << 2
let base = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
defer
{
    base.deallocate(capacity: -314159) // anything goes
}
var pixel:UnsafeMutablePointer<UInt8> = base
while pixel < base + size
{
    pixel.initialize(from: bg_color_rgba, count: 4)
    pixel += 4
}

And it’s convenient to be able to initialize 4 instances at a time without
creating a 4-count buffer pointer. But this doesn’t really contradict my
point if you think carefully. The groups of 4 RGBA values are your atom
here, you’re not really working on 4 instances at a time with a stride 1
UInt8 big, you’re working on 1 single instance at a time with a stride 4
UInt8s big. Writing this is currently painful without the sized
UnsafeMutablePointer API, but I think this is a deficiency of the
UnsafeMutableBufferPointer API and that of its slice type, not a reason for
keeping the sized UnsafeMutablePointer API, which if you ask me is a hack
to get around the fact that we can’t use buffer pointer slices easily.

Anyway, I mention all of this only because everyone seems convinced that
UnsafePointer is supposed to be “edgier” than UnsafeBufferPointer. If you
have ideas to make this better, by all means share them

···

On Fri, Jul 14, 2017 at 2:27 PM, Michael Ilseman <milseman@apple.com> wrote:

On Jul 13, 2017, at 6:55 PM, Taylor Swift via swift-evolution < > swift-evolution@swift.org> wrote:

On Thu, Jul 13, 2017 at 6:56 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 12, 2017, at 12:16 PM, Taylor Swift via swift-evolution < >> swift-evolution@swift.org> wrote:

Hi all, I’ve written up a proposal to modify the unsafe pointer API for
greater consistency, safety, and ease of use.

~~~

Swift currently offers two sets of pointer types — singular pointers such
as UnsafeMutablePointer, and vector (buffer) pointers such as
UnsafeMutable*Buffer*Pointer. This implies a natural separation of tasks
the two kinds of pointers are meant to do. For example, buffer pointers
implement Collection conformance, while singular pointers do not.

However, some aspects of the pointer design contradict these implied
roles. It is possible to allocate an arbitrary number of instances from a
type method on a singular pointer, but not from a buffer pointer. The
result of such an operation returns a singular pointer, even though a
buffer pointer would be more appropriate to capture the information about
the *number* of instances allocated. It’s possible to subscript into a
singular pointer, even though they are not real Collections. Some parts
of the current design turn UnsafePointers into downright *Dangerous*Pointers,
leading users to believe that they have allocated or freed memory when in
fact, they have not.

This proposal seeks to iron out these inconsistencies, and offer a more
convenient, more sensible, and less bug-prone API for Swift pointers.

<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt;

~~~

Thanks for taking time to write this up.

General comments:

UnsafeBufferPointer is an API layer on top of UnsafePointer. The role
of UnsafeBufferPointer is direct memory access sans lifetime
management with Collection semantics. The role of UnsafePointer is
primarily C interop. Those C APIs should be wrapped in Swift APIs that
take UnsafeBufferPointer whenever the pointer represents a C array. I
suppose making UnsafePointer less convenient would push developers
toward UnsafeBufferPointer. I don't think that's worth outright
breaking source, but gradual deprecation of convenience methods, like
`susbscript` might be acceptable.

Gradual deprecation is exactly what I am proposing. As the document states
<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06#proposed-solution&gt;,
the only methods which should be marked immediately as unavailable are the `
deallocate(capacity:)` methods, for safety and source compatibility
reasons. Removing `deallocate(capacity:)` now and forcing a loud compiler
error prevents catastrophic *silent* source breakage in the future, or
worse, from having to *support our own bug*.

I have mixed feelings about stripping UnsafePointer of basic
functionality. Besides breaking source, doing that would be
inconsistent with its role as a lower API layer. The advantage would
just be descreasing API surface area and forcing developers to use a
higher-level API.

UnsafePointer is as much a high level API as UnsafeBufferPointer is. You
wouldn’t create a buffer pointer of length 1 just so you can “stick with
the high level API”. UnsafePointer and UnsafeBufferPointer are two tools
that do related but different things and they can exist at whatever
abstract level you need them at. After all, UnsafeBufferPointer is nothing
but an UnsafePointer? with a length value attached to it. If you’re
allocating more than one instance of memory, you almost certainly need to
track the length of the buffer anyway.

I disagree. UnsafePointer can be viewed as the low level representation of
Swift’s memory model. UnsafeBufferPointer is a convenient facility for
programming at this level, as illustrated by e.g. RandomAccessCollection
conformance. One could drop UnsafeBufferPointer from the standard library
without compromising Swift’s core capabilities, it would just be a large
convenience regression.

UnsafePointer is a low level necessity; UnsafeBufferPointer is a low level
convenience.

The additive changes you propose are fairly obvious. See [SR-3088]

UnsafeMutableBufferPointer doesn't have an allocating init.

I haven't wanted to waste review cycles on small additive
changes. It may make sense to batch them up into one coherent
proposal. Here are a few more to consider.

- [SR-3929] UnsafeBufferPointer should have init from mutable
- [SR-4340] UnsafeBufferPointer needs a withMemoryRebound method
- [SR-3087] No way to arbitrarily initialise an Array's storage

The feature requests you mention are all very valuable, however with
Michael’s point about fixing the memorystate API’s, the size of this
proposal has already grown to encompass dozens of methods in five types. I
think this says a lot about just how broken the current system is

I fail to see how the current system is broken. It accurately reflects
Swift’s memory model while providing the facilities to stay in the bounds
of defined behavior. I do agree that it would be far more convenient for
UnsafeBufferPointer to provide allocation/deallocation and initialization/deinitialization
convenience functionality as well, as I’ve often wanted it myself.

, but I think it’s better to try to fix one class of problems at a time,
and save the less closely-related issues for separate proposals.

These seem very closely related. Isn’t the overall goal of this proposal
to have API parity for UnsafeBufferPointer?

Point by point:

> drop the capacity parameter from UnsafeMutablePointer.allocate() and
deallocate().

I do not agree with removing the capacity parameter and adding a
single-instance allocation API. UnsafePointer was not designed for
single instances, it was primarily designed for C-style arrays. I
don't see the value in providing a different unsafe API for single
vs. multiple values.

Although it’s common to *receive* Unsafe__Pointers from C API’s, it’s rare
to *create* them from the Swift side. 95% of the time your Swift data lives
in a Swift Array, and you use withUnsafePointer(_:) to send them to the C
API, or just pass them directly with Array bridging.

The only example I can think of where I had to allocate memory from the
Swift side to pass to a C API is when I was using the Cairo C library and I
wanted the Swift code to own the image buffer backing the Cairo C structs
and I wanted to manage the memory manually to prevent the buffer backing
from getting deallocated prematurely. I think I ended up using
UnsafeMutableBufferPointer and extracting baseAddresses to manage the
memory. This proposal tries to mitigate that pain of extracting
baseAddresses by giving buffer pointers their own memory management methods.

As for the UnsafePointers you get from C APIs, they almost always come
with a size (or you specify it beforehand with a parameter) so you’re
probably going to be turning them into UnsafeBufferPointers anyway.

I also have to say it’s not common to deallocate something in Swift that
you didn’t previously allocate in Swift.

I’m not sure it is even defined to use `deallocate` on memory that wasn’t
`allocated` in Swift. Andy, thoughts here? Perhaps more clarity in
deallocate’s documentation is needed?

I agree the primary allocation API should be
UnsafeMutableBufferPointer.allocate(capacity:). There is an argument
to be made for removing UnsafeMutablePointer.allocate(capacity:)
entirely. But, as Michael Ilseman pointed out, that would involve
reevaluating several other members of the UnsafePointer API. I think
it's reasonable for UnsafePointer to retain all its functionality as a
lower level API.

I think duplication of functionality is something to be avoided if
possible.

You have to weigh duplication against convenience. If avoiding duplication
is more important, then we should keep the APIs only on UnsafePointer. But,
I would prefer convenience in this scenario.

I don't understand what is misleading about

UnsafePointer.deallocate(capacity:). It *is* inconvenienent for the
user to keep track of memory capacity. Presumably that was done so
either the implementation can move away from malloc/free or some sort
of memory tracking can be implemented on the standard library
side. Obviously, UnsafeBufferPointer.deallocate() would be cleaner in
most cases.

It’s misleading because it plain doesn’t deallocate `capacity` instances.
It deletes the whole memory block regardless of what you pass in the
capacity argument. If the implementation is ever “fixed” so that it
actually deallocates `capacity` instances, suddenly every source that uses
`deallocate(capacity:)` will break, and *no one will know* until their app
starts mysteriously crashing. If the method is not removed, we will have to
support this behavior to avoid breaking sources, and basically say “yes the
argument label says it deallocates a capacity, but what it *really* does is
free the whole block and we can’t fix it because existing code assumes this
behavior”.

Note that many of these things are basically programming at the memory
semantics level, not necessarily at a execution level.

> add an allocate(count:) type method to UnsafeMutableBufferPointer

`capacity` should be used for allocating uninitialized memory not
`count`. `count` should only refer to a number of initialized objects!

We can decide on what the correct term should be, but the current state of
Swift pointers is that *neither* convention is being followed. Just look at
the API for UnsafeMutableRawPointer. It’s a mess. This proposal at the
minimum establishes a consistent convention. It can be revised if you feel
`capacity` is more appropriate than `count`. If what you mean is that it’s
important to maintain the distinction between “initialized counts” and
“uninitialized counts”, well that can be revised in too.

So I just looked over all of the APIs, and they are very consistent in
their use of `count` and `capacity`. Where are you seeing a violation?

> add a deallocate() instance method to UnsafeMutableBufferPointer

Yes, of course! I added a mention of that in SR-3088.

> remove subscripts from UnsafePointer and UnsafeMutablePointer

It's often more clear to perform arithmetic on C array indices rather
than pointers. That said, I'm happy to push developers to use
UnsafeBufferPointer whenever that have a known capacity. To me, this
is a question of whether the benefit of making a dangerous thing less
convenient is worth breaking source compatibility.

Again, I think this is more about what the real use patterns are. If you
are subscripting into a C array with integers, then UnsafeBufferPointer is
the tool for the job, since it give you Collection conformance. If you
can’t make an UnsafeBufferPointer, it’s probably because you don’t know the
length of the array, and so you’re probably iterating through it one
element at a time. UnsafeMutablePointer.successor() is perfect for this
job. If you want to extract or set fields at fixed but irregular offsets,
UnsafeRawPointer is the tool for the job. But I’m hard-pressed to think of
a use case for random access into a singular typed pointer.

I’m also very interested in more convenience facilities for low level
programming and I view UnsafeBufferPointer as the first low level
convenience construct. The point of the very careful dance that the
Unsafe*Pointers go through is to help the programmer avoid unwittingly
falling into undefined behavior.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I think `repeating` is much more clear.

-Andy

···

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of the stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating: 255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to: 255, count: 100)` which is why I left this idea out of the proposal. Now that you mention the problem with `initializeMemory<Element>(as:at:count:to:)`, it might be a good idea to add this rename back into it.

The conversion you’re talking about should be handled by the compiler.

public func get<T>(_ p: UnsafePointer<T>) -> T {
  return p.pointee
}

public func foo<T>(p: UnsafeMutablePointer<T>) {
  _ = get(p)
}

Or are you thinking of a different use case?

-Andy

···

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I'm not sure removing the need for implicit casts is a goal. I did
that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

Yes, I can imagine initializing a mutable pointer to some values, and then wanting to use that pointer as a source to initialize more buffers. Having to convert a mutable pointer to an immutable pointer is annoying because a function that takes an immutable pointer obviously shouldn’t care if the pointer could be mutated anyway. It’s like having to rebind a `var` variable to a `let` constant before passing it as any non-inout argument to a function, since function parameters are immutable. At any rate, this only applies to two out of the seven memorystate operations, so comparably, it’s not a big API expansion at all.

No, I just forgot about the compiler subtyping relationship. Never mind
that part then lol

···

On Tue, Jul 18, 2017 at 4:40 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I'm not sure removing the need for implicit casts is a goal. I did

that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

Yes, I can imagine initializing a mutable pointer to some values, and then
wanting to use that pointer as a source to initialize more buffers. Having
to convert a mutable pointer to an immutable pointer is annoying because a
function that takes an immutable pointer obviously shouldn’t care if the
pointer could be mutated anyway. It’s like having to rebind a `var`
variable to a `let` constant before passing it as any non-inout argument
to a function, since function parameters are immutable. At any rate, this
only applies to two out of the seven memorystate operations, so comparably,
it’s not a big API expansion at all.

The conversion you’re talking about should be handled by the compiler.

public func get<T>(_ p: UnsafePointer<T>) -> T {
  return p.pointee
}

public func foo<T>(p: UnsafeMutablePointer<T>) {
  _ = get(p)
}

Or are you thinking of a different use case?

-Andy

I agree with all of Andy’s points. I really like this and think it’s a
good time to start discussing its details and moving from a pitch to a
proposal. Thank you for writing it!

Minor tweak: say “deprecate” instead of “remove” for APIs, which has a
better connotation with respect to source compatibility. While I want to
have the best APIs, it’s important to make migration smooth. For example,
see https://github.com/apple/swift-evolution/blob/master/proposals/
0180-string-index-overhaul.md#source-compatibility

The main thing that I think needs to be massaged before a formal proposal
is the introduction and motivation section. It contains hyperbole that is
distracting and misleading. Some examples:

> Introduction
> …
> but the current API design is not very safe, consistent, or convenient.

This proposal does not address “safe” or unsafety. I think the proposal is
very good and important for addressing consistency and convenience, which
help encourage programmers to use APIs correctly, but “not very safe” is
orthogonal to the proposal.

> In some places, this design turns UnsafePointers into
outright DangerousPointers, leading users to believe that they have
allocated or freed memory when in fact, they have not.

I see nothing in this proposal that identifies, nor address UnsafePointers
as being “DangerousPointers”. This proposal seeks to change idiomatic use
to be more consistent and convenient, which is very important, but does not
change what “Unsafe” means in Swift. Near as I can tell, the semantics and
“dangerousness" of Unsafe*Pointers are unchanged by this proposal.

> The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices.

I agree with everything up until “excessively unsafe programming
practices”. What do you mean by “unsafe”? What practice is that? It seems
like you might have a very different definition of Unsafe than Swift does.
If so, then this will be a very different sort of proposal and you should
identify what you mean by “unsafe”.

The references to unsafety are about the signature of `deallocate(capacity:)`.
It’s unsafe because you can write perfectly legal Swift code that
*shouldn’t* segfault, but still can, for example

var ptr = UnsafeMutablePointer<UInt8>.allocate(capacity: 1000000)
ptr.initialize(to: 13, count: 1000000)
ptr.deallocate(capacity: 500000) // deallocate the second half of the
memory block
ptr[0] // signal SIGSEGV: invalid address (fault address: 0x7ffff7e79010)

The first 500000 addresses should still be valid if the documentation
<https://developer.apple.com/documentation/swift/unsafemutablepointer/2295090-deallocate&gt;
is to be read literally.

As to the rest of your comments, I agree the language needs to be cleaned
up, right now the document is really more of a working draft to get my
points across until there’s more of less consensus around what should be in
it.

> This proposal seeks to iron out these inconsistencies, and offer a more
convenient, more sensible, and less bug-prone API for Swift pointers.

100% agree and I’m super enthusiastic for this proposal for this reason!
My main feedback is to align the motivation and pitch with the message,
unless you really do have a different definition of “unsafe” that you’re
wanting to pitch.

> This results in an equally elegant API with about one-third less surface
area.

:tada:

> Motivation:
> Right now, UnsafeMutableBufferPointer is kind of a black box. To do
anything with the memory block it represents, you have to extract
baseAddresses and counts. This is unfortunate because
UnsafeMutableBufferPointer provides a handy container for tracking the size
of a memory buffer, but to actually make use of this information, the
buffer pointer must be disassembled.

Note that Unsafe*BufferPointer conforms to RandomAccessCollection and thus
gets all the same conveniences of anything else that is Array-like. This
means that after it has been properly allocated, initialized, and
pointer-casted, it is very convenient for *consumers* of Unsafe*BufferPointers.
The main pain points, and this proposal is excellent at addressing, are on
the *producers* of Unsafe*BufferPointers. For producers, there are a lot
of rules and hoops to jump through and the APIs are not conveniently
aligned with them. I do think you’ve done a great job of correctly
identifying the pain points for people who need to produce
Unsafe*BufferPointers.

Good point

···

On Tue, Jul 18, 2017 at 4:41 PM, Michael Ilseman <milseman@apple.com> wrote:

The rest of the motivation section is excellent! I have done every single
“idiom” you highlight and hated having to do it.

On Jul 18, 2017, at 11:19 AM, Andrew Trick via swift-evolution < > swift-evolution@swift.org> wrote:

On Jul 17, 2017, at 10:06 PM, Taylor Swift via swift-evolution < > swift-evolution@swift.org> wrote:

I’ve drafted a new version of the unsafe pointer proposal based on
feedback I’ve gotten from this thread. You can read it here
<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;\.

~~~

Swift’s pointer types are an important interface for low-level memory
manipulation, but the current API design is not very safe, consistent, or
convenient. Many memory methods demand a capacity: or count: argument,
forcing the user to manually track the size of the memory block, even
though most of the time this is either unnecessary, or redundant as buffer
pointers track this information natively. In some places, this design turns
UnsafePointers into outright *Dangerous*Pointers, leading users to
believe that they have allocated or freed memory when in fact, they have
not.

The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices. This proposal seeks to iron out
these inconsistencies, and offer a more convenient, more sensible, and less
bug-prone API for Swift pointers.

The previous draft
<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt; of
this proposal was relatively source-breaking, calling for a separation of
functionality between singular pointer types and vector (buffer) pointer
types. This proposal instead separates functionality between
internally-tracked length pointer types and externally-tracked length
pointer types. This results in an equally elegant API with about one-third
less surface area.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

~~~

> remove the capacity parameter from deallocate(capacity:) and
deallocate(bytes:alignedTo:)

That's probably for the best.

> add unsized memory methods to UnsafeMutableBufferPointer

Yay!

> add an assign(to:count:) method to UnsafeMutablePointer and an
assign(to:) method to UnsafeMutableBufferPointer

Sure.

> add a default value of 1 to all size parameters on UnsafeMutablePointer
and applicable
> size parameters on UnsafeMutableRawPointer

I'm not opposed to it.

> rename copyBytes(from:count:) to copy(from:bytes:)

LGTM in the interest of consistency. I should not have caved on this the
first time around.

> bytes refers to, well, a byte quantity that is not assumed to be
initialized.
> capacity refers to a strided quantity that is not assumed to be
initialized.
> count refers to a strided quantity that is assumed to be initialized.

That's how I see it.

> rename count in UnsafeMutableRawBufferPointer.allocate(count:) to bytes
and add an
> alignedTo parameter to make it UnsafeMutableRawBufferPointer.
allocate(bytes:alignedTo:)

Memory allocation is an issue unto itself. I generally prefer your
proposed API. However...

1. Larger-than-pointer alignments aren't currently respected.

2. Users virtually never want to specify the alignment explicitly. They
   just want platform alignment. Unfortunately, there's no reasonable
   "maximal" alignment to use as a default. I think pointer-alignment
   is an excellent default guarantee.

3. The current allocation builtins seem to presume that
   allocation/deallocation can be made more efficient if the user code
   specifies alignment at deallocation. I don't think
   UnsafeRawBufferPointer should expose that to the user, so I agree
   with your proposal. In fact, I think aligned `free` should be
   handled within the Swift runtime.

Resolving these issues requires changes to the Swift runtime API and
implementation. This might be a good time to revisit that design, but
it might slow down the rest of the proposal.

> fix the ordering of the arguments in initializeMemory<Element>(as:
at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

> add the sized memorystate functions withMemoryRebound<Element,
>(to:count:_:) to
> UnsafeMutableBufferPointer, and initializeMemory<Element>(as:
at:to:count:),
> initializeMemory<Element>(as:from:count:) moveInitializeMemory<Element>(
as:from:count:),
> and bindMemory<Element>(to:count:) to UnsafeMutableRawBufferPointer

Yay!

> add mutable overloads to non-vacating memorystate method arguments

I'm not sure removing the need for implicit casts is a goal. I did
that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

> add a init(mutating:) initializer to UnsafeMutableBufferPointer

Yes, finally.

> remove initialize<C>(from:) from UnsafeMutablePointer

Yep.

> adding an initializer UnsafeMutableBufferPointer<
>.init(allocatingCount:) instead > of a type method to
UnsafeMutableBufferPointer

For the record, I strongly prefer a type method for allocation for the
reason you mention, it has important side effects beyond simply
initializingn the pointer.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

How do we feel about changing the label to `repeated:`, even in cases where
`count:` is 1?

This would mean that calls would read like this: `ptr.initialize(repeated:
value)`

A grep through the stdlib shows that this is by far the most common use
case:

swift-source/swift$ grep initialize\(to: . -r
./stdlib/private/SwiftPrivatePthreadExtras/SwiftPrivatePthreadExtras.swift:
result.initialize(to: block(arg))
./stdlib/private/StdlibUnittest/OpaqueIdentityFunctions.swift:
ptr.initialize(to: x)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
anyPointer.initialize(to: any)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction0(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction1(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction2(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction3(function: function))
./stdlib/private/SwiftPrivate/ShardedAtomicCounter.swift: (shards +
i).initialize(to: 0)
./stdlib/public/SDK/Foundation/NSError.swift: out.initialize(to: bridged)
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(self.assumingMemoryBound(to: T.self) + i).initialize(to: source[i])
./stdlib/public/core/UnsafeRawPointer.swift.gyb: // .initialize(to:
(source + i).move())
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(--dst).initialize(to: (--src).move())
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: repeatedValue)
./stdlib/public/core/Arrays.swift.gyb: (_buffer.firstElementAddress +
oldCount).initialize(to: newElement)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: x)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: newValues[q])
./stdlib/public/core/Arrays.swift.gyb: (base +
newCount).initialize(to: next)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: target.initialize(to:
AnyHashable(value))
./stdlib/public/core/ArrayCast.swift: target.initialize(to:
_arrayForceCast(source.pointee))
./stdlib/public/core/ArrayCast.swift: target.initialize(to: result)
./stdlib/public/core/ContiguousArrayBuffer.swift: (resultPtr +
i).initialize(to: _bridgeAnythingToObjectiveC(p[i]))
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
source[i])
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
element)
./stdlib/public/core/ArrayBuffer.swift: result.initialize(to:
result.pointee)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_setDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
result)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_dictionaryDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
result)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (values +
i).initialize(to: v)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (values +
toEntryAt).initialize(to: (from.values + at).move())
./stdlib/public/core/ReflectionLegacy.swift: out.initialize(to:
String(reflecting: x))

./stdlib/public/core/ManagedBuffer.swift: p.headerAddress.initialize(to:
initHeaderVal)
./stdlib/public/core/ManagedBuffer.swift: $0.initialize(to:
./stdlib/public/core/ArrayBufferProtocol.swift: (elements +
j).initialize(to: newValues[i])
./stdlib/public/core/Sequence.swift: ptr.initialize(to: x)
./stdlib/public/core/HeapBuffer.swift: $0.initialize(to:
Header(initializer))
./stdlib/public/core/UnsafeBitMap.swift: values.initialize(to: 0, count:
numberOfWords)
./stdlib/public/core/String.swift: resultStorage.initialize(to:
./test/api-digester/stdlib-stable.json: "printedName":
"initialize(to:count:)",
./test/Generics/slice_test.swift: (newbase + i).initialize(to:
(base+i).move())
./test/Generics/slice_test.swift: (base+length).initialize(to: elem)
./test/Parse/pointer_conversion.swift.gyb: p.initialize(to: t)

./test/stdlib/Reflection.swift:sanePointerString.initialize(to: "Hello
panda")

./test/stdlib/Builtins.swift: (buf + 0).initialize(to: value)
./test/stdlib/Builtins.swift: (buf + 1).initialize(to: value)
./test/stdlib/Renames.swift: x.initialize(e) // expected-error
{{'initialize' has been renamed to 'initialize(to:)'}} {{5-15=initialize}}
{{16-16=to: }} {{none}}
./test/stdlib/Renames.swift: x.initialize(with: e) // expected-error
{{'initialize(with:count:)' has been renamed to 'initialize(to:count:)'}}
{{5-15=initialize}} {{16-20=to}} {{none}}
./test/stdlib/Renames.swift: ptr1.initialize(with: e, count: 1) //
expected-error {{'initialize(with:count:)' has been renamed to
'initialize(to:count:)'}} {{8-18=initialize}} {{19-23=to}} {{none}}
./test/Sanitizers/asan.swift:a.initialize(to: 5)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/StringSlicesConcurrentAppend.swift:
barrierVar!.initialize(to: _stdlib_pthread_barrier_t())
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyVector(layout: layout) + keyCount).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueVector(layout: layout) + keyCount).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
keyArray.initialize(to: key0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + 1).initialize(to: key1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
valueArray.initialize(to: value0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + 1).initialize(to: value1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyArray + i).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray(layout: layout) + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + layout.keyCount - 1).initialize(to: appendKey)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + layout.keyCount - 1).initialize(to: appendValue)
./validation-test/compiler_crashers_2_fixed/0004-rdar20564605.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0003-rdar20564378.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0006-rdar20588474.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0010-rdar20638881.swift:
p.initialize(to: element)

···

On Tue, Jul 18, 2017 at 4:33 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:a

t:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of the
stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating:
255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to:
255, count: 100)` which is why I left this idea out of the proposal. Now
that you mention the problem with `initializeMemory<Element>(as:
at:count:to:)`, it might be a good idea to add this rename back into it.

I think `repeating` is much more clear.

-Andy

Revised the document with these suggestions, notably I clarified some of
the language about memory safety, and specified that mutable overloads will
only be added to UnsafeMutableBufferPointer and
UnsafeMutableRawBufferPointer, not their plain variants. It also picks `
repeating:` as the argument label to replace `to:` in functions like `
initialize(to:count:)`.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

···

On Tue, Jul 18, 2017 at 4:41 PM, Michael Ilseman <milseman@apple.com> wrote:

I agree with all of Andy’s points. I really like this and think it’s a
good time to start discussing its details and moving from a pitch to a
proposal. Thank you for writing it!

Minor tweak: say “deprecate” instead of “remove” for APIs, which has a
better connotation with respect to source compatibility. While I want to
have the best APIs, it’s important to make migration smooth. For example,
see https://github.com/apple/swift-evolution/blob/master/proposals/
0180-string-index-overhaul.md#source-compatibility

The main thing that I think needs to be massaged before a formal proposal
is the introduction and motivation section. It contains hyperbole that is
distracting and misleading. Some examples:

> Introduction
> …
> but the current API design is not very safe, consistent, or convenient.

This proposal does not address “safe” or unsafety. I think the proposal is
very good and important for addressing consistency and convenience, which
help encourage programmers to use APIs correctly, but “not very safe” is
orthogonal to the proposal.

> In some places, this design turns UnsafePointers into
outright DangerousPointers, leading users to believe that they have
allocated or freed memory when in fact, they have not.

I see nothing in this proposal that identifies, nor address UnsafePointers
as being “DangerousPointers”. This proposal seeks to change idiomatic use
to be more consistent and convenient, which is very important, but does not
change what “Unsafe” means in Swift. Near as I can tell, the semantics and
“dangerousness" of Unsafe*Pointers are unchanged by this proposal.

> The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices.

I agree with everything up until “excessively unsafe programming
practices”. What do you mean by “unsafe”? What practice is that? It seems
like you might have a very different definition of Unsafe than Swift does.
If so, then this will be a very different sort of proposal and you should
identify what you mean by “unsafe”.

> This proposal seeks to iron out these inconsistencies, and offer a more
convenient, more sensible, and less bug-prone API for Swift pointers.

100% agree and I’m super enthusiastic for this proposal for this reason!
My main feedback is to align the motivation and pitch with the message,
unless you really do have a different definition of “unsafe” that you’re
wanting to pitch.

> This results in an equally elegant API with about one-third less surface
area.

:tada:

> Motivation:
> Right now, UnsafeMutableBufferPointer is kind of a black box. To do
anything with the memory block it represents, you have to extract
baseAddresses and counts. This is unfortunate because
UnsafeMutableBufferPointer provides a handy container for tracking the size
of a memory buffer, but to actually make use of this information, the
buffer pointer must be disassembled.

Note that Unsafe*BufferPointer conforms to RandomAccessCollection and thus
gets all the same conveniences of anything else that is Array-like. This
means that after it has been properly allocated, initialized, and
pointer-casted, it is very convenient for *consumers* of Unsafe*BufferPointers.
The main pain points, and this proposal is excellent at addressing, are on
the *producers* of Unsafe*BufferPointers. For producers, there are a lot
of rules and hoops to jump through and the APIs are not conveniently
aligned with them. I do think you’ve done a great job of correctly
identifying the pain points for people who need to produce
Unsafe*BufferPointers.

The rest of the motivation section is excellent! I have done every single
“idiom” you highlight and hated having to do it.

On Jul 18, 2017, at 11:19 AM, Andrew Trick via swift-evolution < > swift-evolution@swift.org> wrote:

On Jul 17, 2017, at 10:06 PM, Taylor Swift via swift-evolution < > swift-evolution@swift.org> wrote:

I’ve drafted a new version of the unsafe pointer proposal based on
feedback I’ve gotten from this thread. You can read it here
<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;\.

~~~

Swift’s pointer types are an important interface for low-level memory
manipulation, but the current API design is not very safe, consistent, or
convenient. Many memory methods demand a capacity: or count: argument,
forcing the user to manually track the size of the memory block, even
though most of the time this is either unnecessary, or redundant as buffer
pointers track this information natively. In some places, this design turns
UnsafePointers into outright *Dangerous*Pointers, leading users to
believe that they have allocated or freed memory when in fact, they have
not.

The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices. This proposal seeks to iron out
these inconsistencies, and offer a more convenient, more sensible, and less
bug-prone API for Swift pointers.

The previous draft
<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt; of
this proposal was relatively source-breaking, calling for a separation of
functionality between singular pointer types and vector (buffer) pointer
types. This proposal instead separates functionality between
internally-tracked length pointer types and externally-tracked length
pointer types. This results in an equally elegant API with about one-third
less surface area.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

~~~

> remove the capacity parameter from deallocate(capacity:) and
deallocate(bytes:alignedTo:)

That's probably for the best.

> add unsized memory methods to UnsafeMutableBufferPointer

Yay!

> add an assign(to:count:) method to UnsafeMutablePointer and an
assign(to:) method to UnsafeMutableBufferPointer

Sure.

> add a default value of 1 to all size parameters on UnsafeMutablePointer
and applicable
> size parameters on UnsafeMutableRawPointer

I'm not opposed to it.

> rename copyBytes(from:count:) to copy(from:bytes:)

LGTM in the interest of consistency. I should not have caved on this the
first time around.

> bytes refers to, well, a byte quantity that is not assumed to be
initialized.
> capacity refers to a strided quantity that is not assumed to be
initialized.
> count refers to a strided quantity that is assumed to be initialized.

That's how I see it.

> rename count in UnsafeMutableRawBufferPointer.allocate(count:) to bytes
and add an
> alignedTo parameter to make it UnsafeMutableRawBufferPointer.
allocate(bytes:alignedTo:)

Memory allocation is an issue unto itself. I generally prefer your
proposed API. However...

1. Larger-than-pointer alignments aren't currently respected.

2. Users virtually never want to specify the alignment explicitly. They
   just want platform alignment. Unfortunately, there's no reasonable
   "maximal" alignment to use as a default. I think pointer-alignment
   is an excellent default guarantee.

3. The current allocation builtins seem to presume that
   allocation/deallocation can be made more efficient if the user code
   specifies alignment at deallocation. I don't think
   UnsafeRawBufferPointer should expose that to the user, so I agree
   with your proposal. In fact, I think aligned `free` should be
   handled within the Swift runtime.

Resolving these issues requires changes to the Swift runtime API and
implementation. This might be a good time to revisit that design, but
it might slow down the rest of the proposal.

> fix the ordering of the arguments in initializeMemory<Element>(as:
at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

> add the sized memorystate functions withMemoryRebound<Element,
>(to:count:_:) to
> UnsafeMutableBufferPointer, and initializeMemory<Element>(as:
at:to:count:),
> initializeMemory<Element>(as:from:count:) moveInitializeMemory<Element>(
as:from:count:),
> and bindMemory<Element>(to:count:) to UnsafeMutableRawBufferPointer

Yay!

> add mutable overloads to non-vacating memorystate method arguments

I'm not sure removing the need for implicit casts is a goal. I did
that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

> add a init(mutating:) initializer to UnsafeMutableBufferPointer

Yes, finally.

> remove initialize<C>(from:) from UnsafeMutablePointer

Yep.

> adding an initializer UnsafeMutableBufferPointer<
>.init(allocatingCount:) instead > of a type method to
UnsafeMutableBufferPointer

For the record, I strongly prefer a type method for allocation for the
reason you mention, it has important side effects beyond simply
initializingn the pointer.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

How would you feel about:

struct UnsafeMutableRawBufferPointer
{

--- static func allocate(count:Int) -> UnsafeMutableRawBufferPointer
+++ static func allocate(bytes:Int, alignedTo:Int) -> UnsafeMutableRawBufferPointer
    func deallocate()
+++ func bindMemory<Element>(to:Element.Type, capacity:Int)
+++ func copy(from:UnsafeRawBufferPointer, bytes:Int)
+++ func initializeMemory<Element>(as:Element.Type, at:Int, to:Element, count:Int)
+++ func initializeMemory<Element>(as:Element.Type, from:UnsafeBufferPointer<Element>, count:Int)
+++ func moveInitializeMemory<Element>(as:Element.Type, from:UnsafeMutableBufferPointer<Element>, count:Int
}

“bytes” = 8 bit quantities (don’t @ me we’re assuming 8 bit bytes)
“capacity” = strided quantities, not assumed to be initialized
“count” = strided quantities, assumed to be initialized

It’s also worth nothing that a lot of what the proposal tries to add to UnsafeBufferPointer is already present in UnsafeMutableRawPointer like a sizeless deallocate() and a sizeless copyBytes(from:).

Although I’m not sure what’s going on with the latter one <https://developer.apple.com/documentation/swift/unsafemutablerawbufferpointer/2635415-copybytes&gt;…lol swiftdoc

Purely in terms of label names, what you have above is perfectly fine.

You’re going to have a problem adding the alignment constraint to buffer pointer, but that’s another topic.

-Andy

···

On Jul 14, 2017, at 9:33 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Fri, Jul 14, 2017 at 1:57 AM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:

On Jul 13, 2017, at 10:30 PM, Taylor Swift <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:

I’m confused I thought we were talking about the naming choices for the argument labels in those functions. I think defining and abiding by consistent meanings for `count`, `capacity`, and `bytes` is a good idea, and it’s part of what this proposal tries to accomplish. Right now half the time we use `count` to refer to “bytes” and half the time we use it to refer to “instances”. The same goes for the word “capacity”. This is all laid out in the document:

“““
Finally, the naming and design of some UnsafeMutableRawPointer members deserves to be looked at. The usage of capacity, bytes, and count as argument labels is wildly inconsistent and confusing. In copyBytes(from:count:), count refers to the number of bytes, while in initializeMemory<T>(as:at:count:to:) and initializeMemory<T>(as:from:count:), count refers to the number of strides. Meanwhile bindMemory<T>(to:capacity:) uses capacity to refer to this quantity. The always-problematic deallocate(bytes:alignedTo) method and allocate(bytes:alignedTo:) type methods use bytes to refer to byte-quantities. Adding to the confusion, UnsafeMutableRawBufferPointer offers an allocate(count:) type method (the same signature method we’re trying to add to UnsafeMutableBufferPointer), except the count in this method refers to bytes. This kind of API naming begets stride bugs and makes Swift needlessly difficult to learn.
”””

The only convenience methods this proposal is trying to add is the functionality on the buffer pointer types. There seems to be broad support for adding this functionality as no one has really opposed that part of the proposal yet. Any other new methods like `UnsafeMutablePointer.assign(to:)` are there for API consistency.

This proposal also calls for getting rid of one of those “redundant initializers” :)

Since we’re not bike-shedding the specifics yet, I’ll just give you some background.

We would ultimately like APIs that allocate and initialize in one go. It’s important that the current lower-level (dangerous) APIs make a clear distinction between initialized and uninitialized memory to avoid confusing them with future (safer) APIs. `capacity` always refers to memory that may be uninitialized. I think that’s very clear and helpful.

In the context of pointers `count` should always be in strides. For raw pointers, that happens to be the same as as `bytes`.

I initially proposed copy(bytes:from:), but someone thought that `bytes` in this particular context did not properly convey the "count of bytes" as opposed to the source of the bytes. You’re right, that’s inconsistent with allocate/deallocate(bytes:), because allocateBytes(count:) would be silly. Just be aware that the inconsistency is a result of over-thinking and excessive bike shedding to the detriment of something that looks nice and is easy to remember.

I should also point out that the inconsistencies in functionality across pointer types, in terms of collection support and other convenience, is also known but was deliberately stripped from proposals as “additive”.

-Andy

I’d also like to swap the ordering of `count:` and `to:` in `
UnsafeMutableRawPointer<T>.initializeMemory(as:at:count:to:)` so it matches
up with the ordering in `UnsafeMutablePointer<T>.initializeMemory(to:count:)
`.

···

On Fri, Jul 14, 2017 at 12:33 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

How would you feel about:

struct UnsafeMutableRawBufferPointer
{

--- static func allocate(count:Int) -> UnsafeMutableRawBufferPointer
+++ static func allocate(bytes:Int, alignedTo:Int) ->
UnsafeMutableRawBufferPointer
    func deallocate()
+++ func bindMemory<Element>(to:Element.Type, capacity:Int)
+++ func copy(from:UnsafeRawBufferPointer, bytes:Int)
+++ func initializeMemory<Element>(as:Element.Type, at:Int, count:Int,
to:Element)
+++ func initializeMemory<Element>(as:Element.Type,
from:UnsafeBufferPointer<Element>, count:Int)
+++ func moveInitializeMemory<Element>(as:Element.Type, from:
UnsafeMutableBufferPointer<Element>, count:Int
}

“bytes” = 8 bit quantities (don’t @ me we’re assuming 8 bit bytes)
“capacity” = strided quantities, not assumed to be initialized
“count” = strided quantities, assumed to be initialized

It’s also worth nothing that a lot of what the proposal tries to add to
UnsafeBufferPointer is already present in UnsafeMutableRawPointer like a
sizeless deallocate() and a sizeless copyBytes(from:).

Although I’m not sure what’s going on with the latter one
<https://developer.apple.com/documentation/swift/unsafemutablerawbufferpointer/2635415-copybytes&gt;…lol
swiftdoc

On Fri, Jul 14, 2017 at 1:57 AM, Andrew Trick <atrick@apple.com> wrote:

On Jul 13, 2017, at 10:30 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I’m confused I thought we were talking about the naming choices for the
argument labels in those functions. I think defining and abiding by
consistent meanings for `count`, `capacity`, and `bytes` is a good idea,
and it’s part of what this proposal tries to accomplish. Right now half the
time we use `count` to refer to “bytes” and half the time we use it to
refer to “instances”. The same goes for the word “capacity”. This is all
laid out in the document:

“““
*Finally, the naming and design of some UnsafeMutableRawPointer members
deserves to be looked at. The usage of capacity, bytes, and count as
argument labels is wildly inconsistent and confusing.
In copyBytes(from:count:), count refers to the number of bytes, while
in initializeMemory<T>(as:at:count:to:) and initializeMemory<T>(as:from:count:), count refers
to the number of strides.
Meanwhile bindMemory<T>(to:capacity:) uses capacity to refer to this
quantity. The always-problematic deallocate(bytes:alignedTo) method
and allocate(bytes:alignedTo:) type methods use bytes to refer to
byte-quantities. Adding to the
confusion, UnsafeMutableRawBufferPointer offers an allocate(count:) type
method (the same signature method we’re trying to add
to UnsafeMutableBufferPointer), except the count in this method refers to
bytes. This kind of API naming begets stride bugs and makes Swift
needlessly difficult to learn.*
”””

The only convenience methods this proposal is trying to add is the
functionality on the buffer pointer types. There seems to be broad support
for adding this functionality as no one has really opposed that part of the
proposal yet. Any other new methods like `UnsafeMutablePointer.assign(t
o:)` are there for API consistency.

This proposal also calls for getting rid of one of those “redundant
initializers” :)

Since we’re not bike-shedding the specifics yet, I’ll just give you some
background.

We would ultimately like APIs that allocate and initialize in one go.
It’s important that the current lower-level (dangerous) APIs make a clear
distinction between initialized and uninitialized memory to avoid confusing
them with future (safer) APIs. `capacity` always refers to memory that may
be uninitialized. I think that’s very clear and helpful.

In the context of pointers `count` should always be in strides. For raw
pointers, that happens to be the same as as `bytes`.

I initially proposed copy(bytes:from:), but someone thought that `bytes`
in this particular context did not properly convey the "count of bytes" as
opposed to the source of the bytes. You’re right, that’s inconsistent with
allocate/deallocate(bytes:), because allocateBytes(count:) would be silly.
Just be aware that the inconsistency is a result of over-thinking and
excessive bike shedding to the detriment of something that looks nice and
is easy to remember.

I should also point out that the inconsistencies in functionality across
pointer types, in terms of collection support and other convenience, is
also known but was deliberately stripped from proposals as “additive”.

-Andy

For the sake of argument, I’m gonna claim that instead, UnsafeMutableBufferPointer is the low level necessity, and UnsafeMutablePointer is the low level convenience structure.

Suppose that every block of memory has a starting point, and a length. An UnsafeBufferPointer represents that, since all an UnsafeBufferPointer is, is a start address, and a length value, bundled together into one variable instead of two. Then we can say that UnsafePointer is a special case of UnsafeBufferPointer, where the length value is unknown.

Mind you I didn’t say that the length value equals 1. But it might as well be. Because when the length is unknown, the only index where you can reasonably expect there to be a valid value is index zero. You wouldn’t feel confident trying to do something to the memory address 3 strides down from the pointer. If you did feel confident doing that, well now you have a length value. The length value is 4. Your regular Pointer is now a BufferPointer.

The only reason any of this matters, is because all of our memory and memorystate -related functions take size arguments. You have to fill in the size argument with something. That something is the buffer `count`. Even if you don’t actually know the number for the `count`, you still have to supply some value, in which case the “only” sensible choice is “1”. That’s why you could argue that UnsafePointer is just a special case of a buffer pointer where `count` is 1. The UnsafePointer API demands information that only BufferPointers know. You could design an API where buffer pointers are the only pointers that exist, and everything would still work fine. Just because they have high-level capabilities doesn’t mean they can’t do everything plain pointers can do just as efficiently. A plain pointer takes up one word of storage, and you use another word of storage yourself to track the size. A buffer pointer stores the two words next to each other. You could ignore the size word and track the size yourself, taking up three words of storage, but that would just be silly.

But wait! I don’t need to track the size, you say! The pointer just points to one item! I just need the address word, not the size word! Well, yeah. That’s why I proposed a sizeless singular pointer API, so you don’t have to go around plugging “1”s everywhere and save yourself a word of storage. You can argue that’s the actual high-level API, since it abstracts away the length stuff. But if “1” isn’t your answer to the question “on how many instances should this method operate on”, go back to paragraph 3 and convince yourself that what you really have is an buffer pointer, not a plain pointer.

Now I thought of an exception to this, like when you’re assigning 8-bit RGBA values to an image buffer and it might make sense to write something like this in the current API:

let size:Int = height * width << 2
let base = UnsafeMutablePointer<UInt8>.allocate(capacity: size)
defer
{
    base.deallocate(capacity: -314159) // anything goes
}
var pixel:UnsafeMutablePointer<UInt8> = base
while pixel < base + size
{
    pixel.initialize(from: bg_color_rgba, count: 4)
    pixel += 4
}

And it’s convenient to be able to initialize 4 instances at a time without creating a 4-count buffer pointer. But this doesn’t really contradict my point if you think carefully. The groups of 4 RGBA values are your atom here, you’re not really working on 4 instances at a time with a stride 1 UInt8 big, you’re working on 1 single instance at a time with a stride 4 UInt8s big. Writing this is currently painful without the sized UnsafeMutablePointer API, but I think this is a deficiency of the UnsafeMutableBufferPointer API and that of its slice type, not a reason for keeping the sized UnsafeMutablePointer API, which if you ask me is a hack to get around the fact that we can’t use buffer pointer slices easily.

Anyway, I mention all of this only because everyone seems convinced that UnsafePointer is supposed to be “edgier” than UnsafeBufferPointer. If you have ideas to make this better, by all means share them

UnsafeBufferPointer was developed to add safety and convenience on top of UnsafePointer. That’s just a historical fact, not necessarily detracting from your argument.

The problem with your use case above is that UnsafePointer doesn’t get the debug-mode bounds checks.

There’s an argument to be made for keeping the UnsafePointer API complete (as a single point of truth for the memory model), and an argument to be made for removing it (it’s not the *right* API to use in nearly all cases). I would be fine with either approach and people can argue it out. At the moment I would lean toward not breaking source for something that isn’t a critical problem for the language.

That doesn’t need to be resolved to make UnsafeBufferPointer clean and convenient.

-Andy

···

On Jul 14, 2017, at 7:24 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

On Fri, Jul 14, 2017 at 2:27 PM, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

On Jul 13, 2017, at 6:55 PM, Taylor Swift via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Thu, Jul 13, 2017 at 6:56 PM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:

On Jul 12, 2017, at 12:16 PM, Taylor Swift via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Hi all, I’ve written up a proposal to modify the unsafe pointer API for greater consistency, safety, and ease of use.

~~~

Swift currently offers two sets of pointer types — singular pointers such as UnsafeMutablePointer, and vector (buffer) pointers such as UnsafeMutableBufferPointer. This implies a natural separation of tasks the two kinds of pointers are meant to do. For example, buffer pointers implement Collection conformance, while singular pointers do not.

However, some aspects of the pointer design contradict these implied roles. It is possible to allocate an arbitrary number of instances from a type method on a singular pointer, but not from a buffer pointer. The result of such an operation returns a singular pointer, even though a buffer pointer would be more appropriate to capture the information about the number of instances allocated. It’s possible to subscript into a singular pointer, even though they are not real Collections. Some parts of the current design turn UnsafePointers into downright DangerousPointers, leading users to believe that they have allocated or freed memory when in fact, they have not.

This proposal seeks to iron out these inconsistencies, and offer a more convenient, more sensible, and less bug-prone API for Swift pointers.

<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt;

~~~

Thanks for taking time to write this up.

General comments:

UnsafeBufferPointer is an API layer on top of UnsafePointer. The role
of UnsafeBufferPointer is direct memory access sans lifetime
management with Collection semantics. The role of UnsafePointer is
primarily C interop. Those C APIs should be wrapped in Swift APIs that
take UnsafeBufferPointer whenever the pointer represents a C array. I
suppose making UnsafePointer less convenient would push developers
toward UnsafeBufferPointer. I don't think that's worth outright
breaking source, but gradual deprecation of convenience methods, like
`susbscript` might be acceptable.

Gradual deprecation is exactly what I am proposing. As the document states <https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06#proposed-solution&gt;, the only methods which should be marked immediately as unavailable are the `deallocate(capacity:)` methods, for safety and source compatibility reasons. Removing `deallocate(capacity:)` now and forcing a loud compiler error prevents catastrophic *silent* source breakage in the future, or worse, from having to *support our own bug*.

I have mixed feelings about stripping UnsafePointer of basic
functionality. Besides breaking source, doing that would be
inconsistent with its role as a lower API layer. The advantage would
just be descreasing API surface area and forcing developers to use a
higher-level API.

UnsafePointer is as much a high level API as UnsafeBufferPointer is. You wouldn’t create a buffer pointer of length 1 just so you can “stick with the high level API”. UnsafePointer and UnsafeBufferPointer are two tools that do related but different things and they can exist at whatever abstract level you need them at. After all, UnsafeBufferPointer is nothing but an UnsafePointer? with a length value attached to it. If you’re allocating more than one instance of memory, you almost certainly need to track the length of the buffer anyway.

I disagree. UnsafePointer can be viewed as the low level representation of Swift’s memory model. UnsafeBufferPointer is a convenient facility for programming at this level, as illustrated by e.g. RandomAccessCollection conformance. One could drop UnsafeBufferPointer from the standard library without compromising Swift’s core capabilities, it would just be a large convenience regression.

UnsafePointer is a low level necessity; UnsafeBufferPointer is a low level convenience.

The additive changes you propose are fairly obvious. See [SR-3088]
UnsafeMutableBufferPointer doesn't have an allocating init.

I haven't wanted to waste review cycles on small additive
changes. It may make sense to batch them up into one coherent
proposal. Here are a few more to consider.

- [SR-3929] UnsafeBufferPointer should have init from mutable
- [SR-4340] UnsafeBufferPointer needs a withMemoryRebound method
- [SR-3087] No way to arbitrarily initialise an Array's storage

The feature requests you mention are all very valuable, however with Michael’s point about fixing the memorystate API’s, the size of this proposal has already grown to encompass dozens of methods in five types. I think this says a lot about just how broken the current system is

I fail to see how the current system is broken. It accurately reflects Swift’s memory model while providing the facilities to stay in the bounds of defined behavior. I do agree that it would be far more convenient for UnsafeBufferPointer to provide allocation/deallocation and initialization/deinitialization convenience functionality as well, as I’ve often wanted it myself.

, but I think it’s better to try to fix one class of problems at a time, and save the less closely-related issues for separate proposals.

These seem very closely related. Isn’t the overall goal of this proposal to have API parity for UnsafeBufferPointer?

Point by point:

> drop the capacity parameter from UnsafeMutablePointer.allocate() and deallocate().

I do not agree with removing the capacity parameter and adding a
single-instance allocation API. UnsafePointer was not designed for
single instances, it was primarily designed for C-style arrays. I
don't see the value in providing a different unsafe API for single
vs. multiple values.

Although it’s common to *receive* Unsafe__Pointers from C API’s, it’s rare to *create* them from the Swift side. 95% of the time your Swift data lives in a Swift Array, and you use withUnsafePointer(_:) to send them to the C API, or just pass them directly with Array bridging.

The only example I can think of where I had to allocate memory from the Swift side to pass to a C API is when I was using the Cairo C library and I wanted the Swift code to own the image buffer backing the Cairo C structs and I wanted to manage the memory manually to prevent the buffer backing from getting deallocated prematurely. I think I ended up using UnsafeMutableBufferPointer and extracting baseAddresses to manage the memory. This proposal tries to mitigate that pain of extracting baseAddresses by giving buffer pointers their own memory management methods.

As for the UnsafePointers you get from C APIs, they almost always come with a size (or you specify it beforehand with a parameter) so you’re probably going to be turning them into UnsafeBufferPointers anyway.

I also have to say it’s not common to deallocate something in Swift that you didn’t previously allocate in Swift.

I’m not sure it is even defined to use `deallocate` on memory that wasn’t `allocated` in Swift. Andy, thoughts here? Perhaps more clarity in deallocate’s documentation is needed?

I agree the primary allocation API should be
UnsafeMutableBufferPointer.allocate(capacity:). There is an argument
to be made for removing UnsafeMutablePointer.allocate(capacity:)
entirely. But, as Michael Ilseman pointed out, that would involve
reevaluating several other members of the UnsafePointer API. I think
it's reasonable for UnsafePointer to retain all its functionality as a
lower level API.

I think duplication of functionality is something to be avoided if possible.

You have to weigh duplication against convenience. If avoiding duplication is more important, then we should keep the APIs only on UnsafePointer. But, I would prefer convenience in this scenario.

I don't understand what is misleading about
UnsafePointer.deallocate(capacity:). It *is* inconvenienent for the
user to keep track of memory capacity. Presumably that was done so
either the implementation can move away from malloc/free or some sort
of memory tracking can be implemented on the standard library
side. Obviously, UnsafeBufferPointer.deallocate() would be cleaner in
most cases.

It’s misleading because it plain doesn’t deallocate `capacity` instances. It deletes the whole memory block regardless of what you pass in the capacity argument. If the implementation is ever “fixed” so that it actually deallocates `capacity` instances, suddenly every source that uses `deallocate(capacity:)` will break, and *no one will know* until their app starts mysteriously crashing. If the method is not removed, we will have to support this behavior to avoid breaking sources, and basically say “yes the argument label says it deallocates a capacity, but what it *really* does is free the whole block and we can’t fix it because existing code assumes this behavior”.

Note that many of these things are basically programming at the memory semantics level, not necessarily at a execution level.

> add an allocate(count:) type method to UnsafeMutableBufferPointer

`capacity` should be used for allocating uninitialized memory not
`count`. `count` should only refer to a number of initialized objects!

We can decide on what the correct term should be, but the current state of Swift pointers is that *neither* convention is being followed. Just look at the API for UnsafeMutableRawPointer. It’s a mess. This proposal at the minimum establishes a consistent convention. It can be revised if you feel `capacity` is more appropriate than `count`. If what you mean is that it’s important to maintain the distinction between “initialized counts” and “uninitialized counts”, well that can be revised in too.

So I just looked over all of the APIs, and they are very consistent in their use of `count` and `capacity`. Where are you seeing a violation?

> add a deallocate() instance method to UnsafeMutableBufferPointer

Yes, of course! I added a mention of that in SR-3088.

> remove subscripts from UnsafePointer and UnsafeMutablePointer

It's often more clear to perform arithmetic on C array indices rather
than pointers. That said, I'm happy to push developers to use
UnsafeBufferPointer whenever that have a known capacity. To me, this
is a question of whether the benefit of making a dangerous thing less
convenient is worth breaking source compatibility.

Again, I think this is more about what the real use patterns are. If you are subscripting into a C array with integers, then UnsafeBufferPointer is the tool for the job, since it give you Collection conformance. If you can’t make an UnsafeBufferPointer, it’s probably because you don’t know the length of the array, and so you’re probably iterating through it one element at a time. UnsafeMutablePointer.successor() is perfect for this job. If you want to extract or set fields at fixed but irregular offsets, UnsafeRawPointer is the tool for the job. But I’m hard-pressed to think of a use case for random access into a singular typed pointer.

I’m also very interested in more convenience facilities for low level programming and I view UnsafeBufferPointer as the first low level convenience construct. The point of the very careful dance that the Unsafe*Pointers go through is to help the programmer avoid unwittingly falling into undefined behavior.

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

It doesn’t look like there’s an equivalent subtyping relationship between
UnsafeMutableBufferPointer and UnsafeBufferPointer like there is between
the plain pointers. I agree this should be solved in the compiler.

···

On Tue, Jul 18, 2017 at 5:20 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

No, I just forgot about the compiler subtyping relationship. Never mind
that part then lol

On Tue, Jul 18, 2017 at 4:40 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

I'm not sure removing the need for implicit casts is a goal. I did

that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

Yes, I can imagine initializing a mutable pointer to some values, and
then wanting to use that pointer as a source to initialize more buffers.
Having to convert a mutable pointer to an immutable pointer is annoying
because a function that takes an immutable pointer obviously shouldn’t care
if the pointer could be mutated anyway. It’s like having to rebind a `var`
variable to a `let` constant before passing it as any non-inout argument
to a function, since function parameters are immutable. At any rate, this
only applies to two out of the seven memorystate operations, so comparably,
it’s not a big API expansion at all.

The conversion you’re talking about should be handled by the compiler.

public func get<T>(_ p: UnsafePointer<T>) -> T {
  return p.pointee
}

public func foo<T>(p: UnsafeMutablePointer<T>) {
  _ = get(p)
}

Or are you thinking of a different use case?

-Andy

Mmh, I think like the do while -> repeat while change it makes sense, but not enough to displace the obvious meaning of the original... but then again, I lost back then...

···

Sent from my iPhone

Begin forwarded message:

From: Andrew Trick via swift-evolution <swift-evolution@swift.org>
Date: 18 July 2017 at 21:33:31 BST
To: Taylor Swift <kelvin13ma@gmail.com>
Cc: swift-evolution <swift-evolution@swift.org>
Subject: Re: [swift-evolution] Pitch: Improved Swift pointers
Reply-To: Andrew Trick <atrick@apple.com>

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of the stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating: 255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to: 255, count: 100)` which is why I left this idea out of the proposal. Now that you mention the problem with `initializeMemory<Element>(as:at:count:to:)`, it might be a good idea to add this rename back into it.

I think `repeating` is much more clear.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

FWIW, the label should be "repeating".

···

On Tue, Jul 18, 2017 at 11:42 PM, Taylor Swift via swift-evolution < swift-evolution@swift.org> wrote:

How do we feel about changing the label to `repeated:`, even in cases
where `count:` is 1?

This would mean that calls would read like this: `ptr.initialize(repeated:
value)`

A grep through the stdlib shows that this is by far the most common use
case:

swift-source/swift$ grep initialize\(to: . -r
./stdlib/private/SwiftPrivatePthreadExtras/SwiftPrivatePthreadExtras.swift:
result.initialize(to: block(arg))
./stdlib/private/StdlibUnittest/OpaqueIdentityFunctions.swift:
ptr.initialize(to: x)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
anyPointer.initialize(to: any)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction0(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction1(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction2(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction3(function: function))
./stdlib/private/SwiftPrivate/ShardedAtomicCounter.swift: (shards +
i).initialize(to: 0)
./stdlib/public/SDK/Foundation/NSError.swift: out.initialize(to:
bridged)
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(self.assumingMemoryBound(to: T.self) + i).initialize(to: source[i])
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
.initialize(to: (source + i).move())
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(--dst).initialize(to: (--src).move())
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to:
repeatedValue)
./stdlib/public/core/Arrays.swift.gyb: (_buffer.firstElementAddress +
oldCount).initialize(to: newElement)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: x)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: newValues[q])
./stdlib/public/core/Arrays.swift.gyb: (base +
newCount).initialize(to: next)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: result.initialize(to:
value)
./stdlib/public/core/AnyHashable.swift: target.initialize(to:
AnyHashable(value))
./stdlib/public/core/ArrayCast.swift: target.initialize(to:
_arrayForceCast(source.pointee))
./stdlib/public/core/ArrayCast.swift: target.initialize(to: result)
./stdlib/public/core/ContiguousArrayBuffer.swift: (resultPtr +
i).initialize(to: _bridgeAnythingToObjectiveC(p[i]))
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
source[i])
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
element)
./stdlib/public/core/ArrayBuffer.swift: result.initialize(to:
result.pointee)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_setDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb:
target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_dictionaryDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb:
target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (values +
i).initialize(to: v)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (values +
toEntryAt).initialize(to: (from.values + at).move())
./stdlib/public/core/ReflectionLegacy.swift: out.initialize(to:
String(reflecting: x))

./stdlib/public/core/ManagedBuffer.swift:
p.headerAddress.initialize(to: initHeaderVal)
./stdlib/public/core/ManagedBuffer.swift: $0.initialize(to:
./stdlib/public/core/ArrayBufferProtocol.swift: (elements +
j).initialize(to: newValues[i])
./stdlib/public/core/Sequence.swift: ptr.initialize(to: x)
./stdlib/public/core/HeapBuffer.swift: $0.initialize(to:
Header(initializer))
./stdlib/public/core/UnsafeBitMap.swift: values.initialize(to: 0,
count: numberOfWords)
./stdlib/public/core/String.swift: resultStorage.initialize(to:
./test/api-digester/stdlib-stable.json: "printedName":
"initialize(to:count:)",
./test/Generics/slice_test.swift: (newbase + i).initialize(to:
(base+i).move())
./test/Generics/slice_test.swift: (base+length).initialize(to: elem)
./test/Parse/pointer_conversion.swift.gyb: p.initialize(to: t)

./test/stdlib/Reflection.swift:sanePointerString.initialize(to: "Hello
panda")

./test/stdlib/Builtins.swift: (buf + 0).initialize(to: value)
./test/stdlib/Builtins.swift: (buf + 1).initialize(to: value)
./test/stdlib/Renames.swift: x.initialize(e) // expected-error
{{'initialize' has been renamed to 'initialize(to:)'}} {{5-15=initialize}}
{{16-16=to: }} {{none}}
./test/stdlib/Renames.swift: x.initialize(with: e) // expected-error
{{'initialize(with:count:)' has been renamed to 'initialize(to:count:)'}}
{{5-15=initialize}} {{16-20=to}} {{none}}
./test/stdlib/Renames.swift: ptr1.initialize(with: e, count: 1) //
expected-error {{'initialize(with:count:)' has been renamed to
'initialize(to:count:)'}} {{8-18=initialize}} {{19-23=to}} {{none}}
./test/Sanitizers/asan.swift:a.initialize(to: 5)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/StringSlicesConcurrentAppend.swift:
barrierVar!.initialize(to: _stdlib_pthread_barrier_t())
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyVector(layout: layout) + keyCount).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueVector(layout: layout) + keyCount).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
keyArray.initialize(to: key0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + 1).initialize(to: key1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
valueArray.initialize(to: value0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + 1).initialize(to: value1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyArray + i).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray(layout: layout) + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + layout.keyCount - 1).initialize(to: appendKey)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + layout.keyCount - 1).initialize(to: appendValue)
./validation-test/compiler_crashers_2_fixed/0004-rdar20564605.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0003-rdar20564378.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0006-rdar20588474.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0010-rdar20638881.swift:
p.initialize(to: element)

On Tue, Jul 18, 2017 at 4:33 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:a

t:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of
the stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating:
255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to:
255, count: 100)` which is why I left this idea out of the proposal. Now
that you mention the problem with `initializeMemory<Element>(as:
at:count:to:)`, it might be a good idea to add this rename back into it.

I think `repeating` is much more clear.

-Andy

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

How do we feel about changing the label to `repeated:`, even in cases where `count:` is 1?

This would mean that calls would read like this: `ptr.initialize(repeated: value)`

Right. Given the default `count` of 1, it’s probably best to stick with `to: value`.
-Andy

···

On Jul 18, 2017, at 9:42 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

A grep through the stdlib shows that this is by far the most common use case:

swift-source/swift$ grep initialize\(to: . -r
./stdlib/private/SwiftPrivatePthreadExtras/SwiftPrivatePthreadExtras.swift: result.initialize(to: block(arg))
./stdlib/private/StdlibUnittest/OpaqueIdentityFunctions.swift: ptr.initialize(to: x)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift: anyPointer.initialize(to: any)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift: fn.initialize(to: ThickFunction0(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift: fn.initialize(to: ThickFunction1(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift: fn.initialize(to: ThickFunction2(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift: fn.initialize(to: ThickFunction3(function: function))
./stdlib/private/SwiftPrivate/ShardedAtomicCounter.swift: (shards + i).initialize(to: 0)
./stdlib/public/SDK/Foundation/NSError.swift: out.initialize(to: bridged)
./stdlib/public/core/UnsafeRawPointer.swift.gyb: // (self.assumingMemoryBound(to: T.self) + i).initialize(to: source[i])
./stdlib/public/core/UnsafeRawPointer.swift.gyb: // .initialize(to: (source + i).move())
./stdlib/public/core/UnsafeRawPointer.swift.gyb: // (--dst).initialize(to: (--src).move())
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: repeatedValue)
./stdlib/public/core/Arrays.swift.gyb: (_buffer.firstElementAddress + oldCount).initialize(to: newElement)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: x)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: newValues[q])
./stdlib/public/core/Arrays.swift.gyb: (base + newCount).initialize(to: next)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: target.initialize(to: AnyHashable(value))
./stdlib/public/core/ArrayCast.swift: target.initialize(to: _arrayForceCast(source.pointee))
./stdlib/public/core/ArrayCast.swift: target.initialize(to: result)
./stdlib/public/core/ContiguousArrayBuffer.swift: (resultPtr + i).initialize(to: _bridgeAnythingToObjectiveC(p[i]))
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to: source[i])
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to: element)
./stdlib/public/core/ArrayBuffer.swift: result.initialize(to: result.pointee)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to: _setDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to: _dictionaryDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: (keys + i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (keys + toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (keys + i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (values + i).initialize(to: v)
./stdlib/public/core/HashedCollections.swift.gyb: (keys + toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (values + toEntryAt).initialize(to: (from.values + at).move())
./stdlib/public/core/ReflectionLegacy.swift: out.initialize(to: String(reflecting: x))

./stdlib/public/core/ManagedBuffer.swift: p.headerAddress.initialize(to: initHeaderVal)
./stdlib/public/core/ManagedBuffer.swift: $0.initialize(to:
./stdlib/public/core/ArrayBufferProtocol.swift: (elements + j).initialize(to: newValues[i])
./stdlib/public/core/Sequence.swift: ptr.initialize(to: x)
./stdlib/public/core/HeapBuffer.swift: $0.initialize(to: Header(initializer))
./stdlib/public/core/UnsafeBitMap.swift: values.initialize(to: 0, count: numberOfWords)
./stdlib/public/core/String.swift: resultStorage.initialize(to:
./test/api-digester/stdlib-stable.json: "printedName": "initialize(to:count:)",
./test/Generics/slice_test.swift: (newbase + i).initialize(to: (base+i).move())
./test/Generics/slice_test.swift: (base+length).initialize(to: elem)
./test/Parse/pointer_conversion.swift.gyb: p.initialize(to: t)

./test/stdlib/Reflection.swift:sanePointerString.initialize(to: "Hello panda")

./test/stdlib/Builtins.swift: (buf + 0).initialize(to: value)
./test/stdlib/Builtins.swift: (buf + 1).initialize(to: value)
./test/stdlib/Renames.swift: x.initialize(e) // expected-error {{'initialize' has been renamed to 'initialize(to:)'}} {{5-15=initialize}} {{16-16=to: }} {{none}}
./test/stdlib/Renames.swift: x.initialize(with: e) // expected-error {{'initialize(with:count:)' has been renamed to 'initialize(to:count:)'}} {{5-15=initialize}} {{16-20=to}} {{none}}
./test/stdlib/Renames.swift: ptr1.initialize(with: e, count: 1) // expected-error {{'initialize(with:count:)' has been renamed to 'initialize(to:count:)'}} {{8-18=initialize}} {{19-23=to}} {{none}}
./test/Sanitizers/asan.swift:a.initialize(to: 5)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb: allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb: allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/StringSlicesConcurrentAppend.swift: barrierVar!.initialize(to: _stdlib_pthread_barrier_t())
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (_keyVector(layout: layout) + keyCount).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (_valueVector(layout: layout) + keyCount).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (_valueArray + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: keyArray.initialize(to: key0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (keyArray + 1).initialize(to: key1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: valueArray.initialize(to: value0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (valueArray + 1).initialize(to: value1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (_keyArray + i).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (_valueArray(layout: layout) + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (keyArray + layout.keyCount - 1).initialize(to: appendKey)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb: (valueArray + layout.keyCount - 1).initialize(to: appendValue)
./validation-test/compiler_crashers_2_fixed/0004-rdar20564605.swift: p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0003-rdar20564378.swift: p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0006-rdar20588474.swift: p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0010-rdar20638881.swift: p.initialize(to: element)

On Tue, Jul 18, 2017 at 4:33 PM, Andrew Trick <atrick@apple.com <mailto:atrick@apple.com>> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com <mailto:kelvin13ma@gmail.com>> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of the stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating: 255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to: 255, count: 100)` which is why I left this idea out of the proposal. Now that you mention the problem with `initializeMemory<Element>(as:at:count:to:)`, it might be a good idea to add this rename back into it.

I think `repeating` is much more clear.

-Andy

Looks awesome!

Super minor tweak:

> Right now, UnsafeMutableBufferPointer is kind of a black box when it comes to producing and modifying instances of it. To do anything except read the elements it contains, you have to extract baseAddresses and counts

You can still modify and reassign elements through the subscript, iterators, MutableRandomAccessSlice, etc. The point here is that creating and setting it up is a big pain compared to UnsafeMutablePointer. It’s a little more fair to say “UnsafeMutableBufferPointer is unwieldy for creation, binding, allocation, and element-wise initialization compared to UnsafeMutablePointer."

···

On Jul 19, 2017, at 6:04 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Revised the document with these suggestions, notably I clarified some of the language about memory safety, and specified that mutable overloads will only be added to UnsafeMutableBufferPointer and UnsafeMutableRawBufferPointer, not their plain variants. It also picks `repeating:` as the argument label to replace `to:` in functions like `initialize(to:count:)`.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

On Tue, Jul 18, 2017 at 4:41 PM, Michael Ilseman <milseman@apple.com <mailto:milseman@apple.com>> wrote:

I agree with all of Andy’s points. I really like this and think it’s a good time to start discussing its details and moving from a pitch to a proposal. Thank you for writing it!

Minor tweak: say “deprecate” instead of “remove” for APIs, which has a better connotation with respect to source compatibility. While I want to have the best APIs, it’s important to make migration smooth. For example, see https://github.com/apple/swift-evolution/blob/master/proposals/0180-string-index-overhaul.md#source-compatibility <http://0180-string-index-overhaul.md/#source-compatibility&gt;

The main thing that I think needs to be massaged before a formal proposal is the introduction and motivation section. It contains hyperbole that is distracting and misleading. Some examples:

> Introduction
> …
> but the current API design is not very safe, consistent, or convenient.

This proposal does not address “safe” or unsafety. I think the proposal is very good and important for addressing consistency and convenience, which help encourage programmers to use APIs correctly, but “not very safe” is orthogonal to the proposal.

> In some places, this design turns UnsafePointers into outright DangerousPointers, leading users to believe that they have allocated or freed memory when in fact, they have not.

I see nothing in this proposal that identifies, nor address UnsafePointers as being “DangerousPointers”. This proposal seeks to change idiomatic use to be more consistent and convenient, which is very important, but does not change what “Unsafe” means in Swift. Near as I can tell, the semantics and “dangerousness" of Unsafe*Pointers are unchanged by this proposal.

> The current API suffers from inconsistent naming, poor usage of default argument values, missing methods, and excessive verbosity, and encourages excessively unsafe programming practices.

I agree with everything up until “excessively unsafe programming practices”. What do you mean by “unsafe”? What practice is that? It seems like you might have a very different definition of Unsafe than Swift does. If so, then this will be a very different sort of proposal and you should identify what you mean by “unsafe”.

> This proposal seeks to iron out these inconsistencies, and offer a more convenient, more sensible, and less bug-prone API for Swift pointers.

100% agree and I’m super enthusiastic for this proposal for this reason! My main feedback is to align the motivation and pitch with the message, unless you really do have a different definition of “unsafe” that you’re wanting to pitch.

> This results in an equally elegant API with about one-third less surface area.

:tada:

> Motivation:
> Right now, UnsafeMutableBufferPointer is kind of a black box. To do anything with the memory block it represents, you have to extract baseAddresses and counts. This is unfortunate because UnsafeMutableBufferPointer provides a handy container for tracking the size of a memory buffer, but to actually make use of this information, the buffer pointer must be disassembled.

Note that Unsafe*BufferPointer conforms to RandomAccessCollection and thus gets all the same conveniences of anything else that is Array-like. This means that after it has been properly allocated, initialized, and pointer-casted, it is very convenient for consumers of Unsafe*BufferPointers. The main pain points, and this proposal is excellent at addressing, are on the producers of Unsafe*BufferPointers. For producers, there are a lot of rules and hoops to jump through and the APIs are not conveniently aligned with them. I do think you’ve done a great job of correctly identifying the pain points for people who need to produce Unsafe*BufferPointers.

The rest of the motivation section is excellent! I have done every single “idiom” you highlight and hated having to do it.

On Jul 18, 2017, at 11:19 AM, Andrew Trick via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jul 17, 2017, at 10:06 PM, Taylor Swift via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I’ve drafted a new version of the unsafe pointer proposal based on feedback I’ve gotten from this thread. You can read it here <https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;\.

~~~
Swift’s pointer types are an important interface for low-level memory manipulation, but the current API design is not very safe, consistent, or convenient. Many memory methods demand a capacity: or count: argument, forcing the user to manually track the size of the memory block, even though most of the time this is either unnecessary, or redundant as buffer pointers track this information natively. In some places, this design turns UnsafePointers into outright DangerousPointers, leading users to believe that they have allocated or freed memory when in fact, they have not.

The current API suffers from inconsistent naming, poor usage of default argument values, missing methods, and excessive verbosity, and encourages excessively unsafe programming practices. This proposal seeks to iron out these inconsistencies, and offer a more convenient, more sensible, and less bug-prone API for Swift pointers.

The previous draft <https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt; of this proposal was relatively source-breaking, calling for a separation of functionality between singular pointer types and vector (buffer) pointer types. This proposal instead separates functionality between internally-tracked length pointer types and externally-tracked length pointer types. This results in an equally elegant API with about one-third less surface area.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

~~~

> remove the capacity parameter from deallocate(capacity:) and deallocate(bytes:alignedTo:)

That's probably for the best.

> add unsized memory methods to UnsafeMutableBufferPointer

Yay!

> add an assign(to:count:) method to UnsafeMutablePointer and an assign(to:) method to UnsafeMutableBufferPointer

Sure.

> add a default value of 1 to all size parameters on UnsafeMutablePointer and applicable
> size parameters on UnsafeMutableRawPointer

I'm not opposed to it.

> rename copyBytes(from:count:) to copy(from:bytes:)

LGTM in the interest of consistency. I should not have caved on this the first time around.

> bytes refers to, well, a byte quantity that is not assumed to be initialized.
> capacity refers to a strided quantity that is not assumed to be initialized.
> count refers to a strided quantity that is assumed to be initialized.

That's how I see it.

> rename count in UnsafeMutableRawBufferPointer.allocate(count:) to bytes and add an
> alignedTo parameter to make it UnsafeMutableRawBufferPointer.allocate(bytes:alignedTo:)

Memory allocation is an issue unto itself. I generally prefer your
proposed API. However...

1. Larger-than-pointer alignments aren't currently respected.

2. Users virtually never want to specify the alignment explicitly. They
   just want platform alignment. Unfortunately, there's no reasonable
   "maximal" alignment to use as a default. I think pointer-alignment
   is an excellent default guarantee.

3. The current allocation builtins seem to presume that
   allocation/deallocation can be made more efficient if the user code
   specifies alignment at deallocation. I don't think
   UnsafeRawBufferPointer should expose that to the user, so I agree
   with your proposal. In fact, I think aligned `free` should be
   handled within the Swift runtime.

Resolving these issues requires changes to the Swift runtime API and
implementation. This might be a good time to revisit that design, but
it might slow down the rest of the proposal.

> fix the ordering of the arguments in initializeMemory<Element>(as:at:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

> add the sized memorystate functions withMemoryRebound<Element, Result>(to:count:_:) to
> UnsafeMutableBufferPointer, and initializeMemory<Element>(as:at:to:count:),
> initializeMemory<Element>(as:from:count:) moveInitializeMemory<Element>(as:from:count:),
> and bindMemory<Element>(to:count:) to UnsafeMutableRawBufferPointer

Yay!

> add mutable overloads to non-vacating memorystate method arguments

I'm not sure removing the need for implicit casts is a goal. I did
that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

> add a init(mutating:) initializer to UnsafeMutableBufferPointer

Yes, finally.

> remove initialize<C>(from:) from UnsafeMutablePointer

Yep.

> adding an initializer UnsafeMutableBufferPointer<Element>.init(allocatingCount:) instead > of a type method to UnsafeMutableBufferPointer

For the record, I strongly prefer a type method for allocation for the reason you mention, it has important side effects beyond simply initializingn the pointer.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

What about `value:`?

`ptr.initialize(value: value)`
`ptr.initialize(value: value, count: 13)`
`ptr.initialize(as: UInt16.self, at: 0, value: value, count: 13)`

···

On Wed, Jul 19, 2017 at 12:01 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 9:42 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

How do we feel about changing the label to `repeated:`, even in cases
where `count:` is 1?

This would mean that calls would read like this: `ptr.initialize(repeated:
value)`

Right. Given the default `count` of 1, it’s probably best to stick with
`to: value`.
-Andy

A grep through the stdlib shows that this is by far the most common use
case:

swift-source/swift$ grep initialize\(to: . -r
./stdlib/private/SwiftPrivatePthreadExtras/SwiftPrivatePthreadExtras.swift:
result.initialize(to: block(arg))
./stdlib/private/StdlibUnittest/OpaqueIdentityFunctions.swift:
ptr.initialize(to: x)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
anyPointer.initialize(to: any)
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction0(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction1(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction2(function: function))
./stdlib/private/SwiftReflectionTest/SwiftReflectionTest.swift:
fn.initialize(to: ThickFunction3(function: function))
./stdlib/private/SwiftPrivate/ShardedAtomicCounter.swift: (shards +
i).initialize(to: 0)
./stdlib/public/SDK/Foundation/NSError.swift: out.initialize(to:
bridged)
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(self.assumingMemoryBound(to: T.self) + i).initialize(to: source[i])
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
.initialize(to: (source + i).move())
./stdlib/public/core/UnsafeRawPointer.swift.gyb: //
(--dst).initialize(to: (--src).move())
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to:
repeatedValue)
./stdlib/public/core/Arrays.swift.gyb: (_buffer.firstElementAddress +
oldCount).initialize(to: newElement)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: x)
./stdlib/public/core/Arrays.swift.gyb: p.initialize(to: newValues[q])
./stdlib/public/core/Arrays.swift.gyb: (base +
newCount).initialize(to: next)
./stdlib/public/core/AnyHashable.swift: result.initialize(to: value)
./stdlib/public/core/AnyHashable.swift: result.initialize(to:
value)
./stdlib/public/core/AnyHashable.swift: target.initialize(to:
AnyHashable(value))
./stdlib/public/core/ArrayCast.swift: target.initialize(to:
_arrayForceCast(source.pointee))
./stdlib/public/core/ArrayCast.swift: target.initialize(to: result)
./stdlib/public/core/ContiguousArrayBuffer.swift: (resultPtr +
i).initialize(to: _bridgeAnythingToObjectiveC(p[i]))
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
source[i])
./stdlib/public/core/ContiguousArrayBuffer.swift: p.initialize(to:
element)
./stdlib/public/core/ArrayBuffer.swift: result.initialize(to:
result.pointee)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_setDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb:
target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: target.initialize(to:
_dictionaryDownCast(source.pointee))
./stdlib/public/core/HashedCollections.swift.gyb:
target.initialize(to: result)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
i).initialize(to: k)
./stdlib/public/core/HashedCollections.swift.gyb: (values +
i).initialize(to: v)
./stdlib/public/core/HashedCollections.swift.gyb: (keys +
toEntryAt).initialize(to: (from.keys + at).move())
./stdlib/public/core/HashedCollections.swift.gyb: (values +
toEntryAt).initialize(to: (from.values + at).move())
./stdlib/public/core/ReflectionLegacy.swift: out.initialize(to:
String(reflecting: x))

./stdlib/public/core/ManagedBuffer.swift:
p.headerAddress.initialize(to: initHeaderVal)
./stdlib/public/core/ManagedBuffer.swift: $0.initialize(to:
./stdlib/public/core/ArrayBufferProtocol.swift: (elements +
j).initialize(to: newValues[i])
./stdlib/public/core/Sequence.swift: ptr.initialize(to: x)
./stdlib/public/core/HeapBuffer.swift: $0.initialize(to:
Header(initializer))
./stdlib/public/core/UnsafeBitMap.swift: values.initialize(to: 0,
count: numberOfWords)
./stdlib/public/core/String.swift: resultStorage.initialize(to:
./test/api-digester/stdlib-stable.json: "printedName":
"initialize(to:count:)",
./test/Generics/slice_test.swift: (newbase + i).initialize(to:
(base+i).move())
./test/Generics/slice_test.swift: (base+length).initialize(to: elem)
./test/Parse/pointer_conversion.swift.gyb: p.initialize(to: t)

./test/stdlib/Reflection.swift:sanePointerString.initialize(to: "Hello
panda")

./test/stdlib/Builtins.swift: (buf + 0).initialize(to: value)
./test/stdlib/Builtins.swift: (buf + 1).initialize(to: value)
./test/stdlib/Renames.swift: x.initialize(e) // expected-error
{{'initialize' has been renamed to 'initialize(to:)'}} {{5-15=initialize}}
{{16-16=to: }} {{none}}
./test/stdlib/Renames.swift: x.initialize(with: e) // expected-error
{{'initialize(with:count:)' has been renamed to 'initialize(to:count:)'}}
{{5-15=initialize}} {{16-20=to}} {{none}}
./test/stdlib/Renames.swift: ptr1.initialize(with: e, count: 1) //
expected-error {{'initialize(with:count:)' has been renamed to
'initialize(to:count:)'}} {{8-18=initialize}} {{19-23=to}} {{none}}
./test/Sanitizers/asan.swift:a.initialize(to: 5)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/UnsafeBufferPointer.swift.gyb:
allocated.initialize(to: 1.0, count: count)
./validation-test/stdlib/StringSlicesConcurrentAppend.swift:
barrierVar!.initialize(to: _stdlib_pthread_barrier_t())
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyVector(layout: layout) + keyCount).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueVector(layout: layout) + keyCount).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
keyArray.initialize(to: key0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + 1).initialize(to: key1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
valueArray.initialize(to: value0)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + 1).initialize(to: value1)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_keyArray + i).initialize(to: key)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(_valueArray(layout: layout) + i).initialize(to: value)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(keyArray + layout.keyCount - 1).initialize(to: appendKey)
./validation-test/stdlib/Prototypes/PersistentVector.swift.gyb:
(valueArray + layout.keyCount - 1).initialize(to: appendValue)
./validation-test/compiler_crashers_2_fixed/0004-rdar20564605.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0003-rdar20564378.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0006-rdar20588474.swift:
p.initialize(to: element)
./validation-test/compiler_crashers_2_fixed/0010-rdar20638881.swift:
p.initialize(to: element)

On Tue, Jul 18, 2017 at 4:33 PM, Andrew Trick <atrick@apple.com> wrote:

On Jul 18, 2017, at 11:36 AM, Taylor Swift <kelvin13ma@gmail.com> wrote:

> fix the ordering of the arguments in initializeMemory<Element>(as:a

t:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

This would be a non-issue had we just been consistent with the rest of
the stdlib and named this argument `repeating:` instead of `to:`. But `ptr.initialize(repeating:
255, count: 100)` doesn’t read quite as naturally in English as `ptr.initialize(to:
255, count: 100)` which is why I left this idea out of the proposal. Now
that you mention the problem with `initializeMemory<Element>(as:
at:count:to:)`, it might be a good idea to add this rename back into it.

I think `repeating` is much more clear.

-Andy

Okay, I changed it to

Right now, UnsafeMutableBufferPointer is kind of a black box when it comes
to producing and modifying instances of it. To create, bind, allocate,
initialize, deinitialize, and deallocate them, you have to extract
baseAddresses and counts.

···

On Thu, Jul 20, 2017 at 1:30 PM, Michael Ilseman <milseman@apple.com> wrote:

Looks awesome!

Super minor tweak:

> Right now, UnsafeMutableBufferPointer is kind of a black box when it
comes to producing and modifying instances of it. To do anything except
read the elements it contains, you have to extract baseAddresses and count
s

You can still modify and reassign elements through the subscript,
iterators, MutableRandomAccessSlice, etc. The point here is that creating
and setting it up is a big pain compared to UnsafeMutablePointer. It’s a
little more fair to say “UnsafeMutableBufferPointer is unwieldy for
creation, binding, allocation, and element-wise initialization compared to
UnsafeMutablePointer."

On Jul 19, 2017, at 6:04 PM, Taylor Swift <kelvin13ma@gmail.com> wrote:

Revised the document with these suggestions, notably I clarified some of
the language about memory safety, and specified that mutable overloads will
only be added to UnsafeMutableBufferPointer and
UnsafeMutableRawBufferPointer, not their plain variants. It also picks `
repeating:` as the argument label to replace `to:` in functions like `
initialize(to:count:)`.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

On Tue, Jul 18, 2017 at 4:41 PM, Michael Ilseman <milseman@apple.com> > wrote:

I agree with all of Andy’s points. I really like this and think it’s a
good time to start discussing its details and moving from a pitch to a
proposal. Thank you for writing it!

Minor tweak: say “deprecate” instead of “remove” for APIs, which has a
better connotation with respect to source compatibility. While I want to
have the best APIs, it’s important to make migration smooth. For example,
see https://github.com/apple/swift-evolution/blob/master/proposals/
0180-string-index-overhaul.md#source-compatibility
<http://0180-string-index-overhaul.md/#source-compatibility&gt;

The main thing that I think needs to be massaged before a formal proposal
is the introduction and motivation section. It contains hyperbole that is
distracting and misleading. Some examples:

> Introduction
> …
> but the current API design is not very safe, consistent, or convenient.

This proposal does not address “safe” or unsafety. I think the proposal
is very good and important for addressing consistency and convenience,
which help encourage programmers to use APIs correctly, but “not very safe”
is orthogonal to the proposal.

> In some places, this design turns UnsafePointers into
outright DangerousPointers, leading users to believe that they have
allocated or freed memory when in fact, they have not.

I see nothing in this proposal that identifies, nor address
UnsafePointers as being “DangerousPointers”. This proposal seeks to change
idiomatic use to be more consistent and convenient, which is very
important, but does not change what “Unsafe” means in Swift. Near as I can
tell, the semantics and “dangerousness" of Unsafe*Pointers are unchanged by
this proposal.

> The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices.

I agree with everything up until “excessively unsafe programming
practices”. What do you mean by “unsafe”? What practice is that? It seems
like you might have a very different definition of Unsafe than Swift does.
If so, then this will be a very different sort of proposal and you should
identify what you mean by “unsafe”.

> This proposal seeks to iron out these inconsistencies, and offer a more
convenient, more sensible, and less bug-prone API for Swift pointers.

100% agree and I’m super enthusiastic for this proposal for this reason!
My main feedback is to align the motivation and pitch with the message,
unless you really do have a different definition of “unsafe” that you’re
wanting to pitch.

> This results in an equally elegant API with about one-third less
surface area.

:tada:

> Motivation:
> Right now, UnsafeMutableBufferPointer is kind of a black box. To do
anything with the memory block it represents, you have to extract
baseAddresses and counts. This is unfortunate because
UnsafeMutableBufferPointer provides a handy container for tracking the size
of a memory buffer, but to actually make use of this information, the
buffer pointer must be disassembled.

Note that Unsafe*BufferPointer conforms to RandomAccessCollection and
thus gets all the same conveniences of anything else that is Array-like.
This means that after it has been properly allocated, initialized, and
pointer-casted, it is very convenient for *consumers*
of Unsafe*BufferPointers. The main pain points, and this proposal is
excellent at addressing, are on the *producers* of
Unsafe*BufferPointers. For producers, there are a lot of rules and hoops to
jump through and the APIs are not conveniently aligned with them. I do
think you’ve done a great job of correctly identifying the pain points for
people who need to produce Unsafe*BufferPointers.

The rest of the motivation section is excellent! I have done every single
“idiom” you highlight and hated having to do it.

On Jul 18, 2017, at 11:19 AM, Andrew Trick via swift-evolution < >> swift-evolution@swift.org> wrote:

On Jul 17, 2017, at 10:06 PM, Taylor Swift via swift-evolution < >> swift-evolution@swift.org> wrote:

I’ve drafted a new version of the unsafe pointer proposal based on
feedback I’ve gotten from this thread. You can read it here
<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;\.

~~~

Swift’s pointer types are an important interface for low-level memory
manipulation, but the current API design is not very safe, consistent, or
convenient. Many memory methods demand a capacity: or count: argument,
forcing the user to manually track the size of the memory block, even
though most of the time this is either unnecessary, or redundant as buffer
pointers track this information natively. In some places, this design turns
UnsafePointers into outright *Dangerous*Pointers, leading users to
believe that they have allocated or freed memory when in fact, they have
not.

The current API suffers from inconsistent naming, poor usage of default
argument values, missing methods, and excessive verbosity, and encourages
excessively unsafe programming practices. This proposal seeks to iron out
these inconsistencies, and offer a more convenient, more sensible, and less
bug-prone API for Swift pointers.

The previous draft
<https://gist.github.com/kelvin13/a9c033193a28b1d4960a89b25fbffb06&gt; of
this proposal was relatively source-breaking, calling for a separation of
functionality between singular pointer types and vector (buffer) pointer
types. This proposal instead separates functionality between
internally-tracked length pointer types and externally-tracked length
pointer types. This results in an equally elegant API with about one-third
less surface area.

<https://gist.github.com/kelvin13/1b8ae906be23dff22f7a7c4767f0c907&gt;

~~~

> remove the capacity parameter from deallocate(capacity:) and
deallocate(bytes:alignedTo:)

That's probably for the best.

> add unsized memory methods to UnsafeMutableBufferPointer

Yay!

> add an assign(to:count:) method to UnsafeMutablePointer and an
assign(to:) method to UnsafeMutableBufferPointer

Sure.

> add a default value of 1 to all size parameters on UnsafeMutablePointer
and applicable
> size parameters on UnsafeMutableRawPointer

I'm not opposed to it.

> rename copyBytes(from:count:) to copy(from:bytes:)

LGTM in the interest of consistency. I should not have caved on this the
first time around.

> bytes refers to, well, a byte quantity that is not assumed to be
initialized.
> capacity refers to a strided quantity that is not assumed to be
initialized.
> count refers to a strided quantity that is assumed to be initialized.

That's how I see it.

> rename count in UnsafeMutableRawBufferPointer.allocate(count:) to
bytes and add an
> alignedTo parameter to make it UnsafeMutableRawBufferPointer.
allocate(bytes:alignedTo:)

Memory allocation is an issue unto itself. I generally prefer your
proposed API. However...

1. Larger-than-pointer alignments aren't currently respected.

2. Users virtually never want to specify the alignment explicitly. They
   just want platform alignment. Unfortunately, there's no reasonable
   "maximal" alignment to use as a default. I think pointer-alignment
   is an excellent default guarantee.

3. The current allocation builtins seem to presume that
   allocation/deallocation can be made more efficient if the user code
   specifies alignment at deallocation. I don't think
   UnsafeRawBufferPointer should expose that to the user, so I agree
   with your proposal. In fact, I think aligned `free` should be
   handled within the Swift runtime.

Resolving these issues requires changes to the Swift runtime API and
implementation. This might be a good time to revisit that design, but
it might slow down the rest of the proposal.

> fix the ordering of the arguments in initializeMemory<Element>(as:a
t:count:to:)

I think this ordering was an attempt to avoid confusion with binding
memory where `to` refers to a type. However, it should be consistent
with `UnsafePointer.initialize`, so we need to pick one of those to
change.

> add the sized memorystate functions withMemoryRebound<Element,
>(to:count:_:) to
> UnsafeMutableBufferPointer, and initializeMemory<Element>(as:a
t:to:count:),
> initializeMemory<Element>(as:from:count:)
moveInitializeMemory<Element>(as:from:count:),
> and bindMemory<Element>(to:count:) to UnsafeMutableRawBufferPointer

Yay!

> add mutable overloads to non-vacating memorystate method arguments

I'm not sure removing the need for implicit casts is a goal. I did
that with the pointer `init` methods, but now I think that should be
cleaned up to reduce API surface. I think smaller API surface wins in
these cases. Is there a usability issue you're solving?

> add a init(mutating:) initializer to UnsafeMutableBufferPointer

Yes, finally.

> remove initialize<C>(from:) from UnsafeMutablePointer

Yep.

> adding an initializer UnsafeMutableBufferPointer<Element>.init(allocatingCount:)
instead > of a type method to UnsafeMutableBufferPointer

For the record, I strongly prefer a type method for allocation for the
reason you mention, it has important side effects beyond simply
initializingn the pointer.

-Andy
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution