Using withUnsafePointer on char arrays within C structs


(Russell Finn) #1

Greetings — I haven't found this specific topic in the archives; apologies
if this has come up before.

Suppose I have a C struct with an embedded char array:

struct fileinfo_t {
    char path[256];
};

and I want to convert the path to a Swift String (in order to implement
`description`, say). I would like to use String.init(cString:) for this;
but the `path` field is imported as a tuple of 256 `Int8`s, and I need to
get an `UnsafePointer<CChar>`.

So I would like to write something like this:

extension fileinfo_t: CustomStringConvertible {
    public var description: String {
        let path = withUnsafePointer(to: &self.path) {
            $0.withMemoryRebound(to: CChar.self, capacity:
MemoryLayout.size(ofValue: self.path) {
                $0
            }
        }
        return "fileinfo_t: \(path)"
    }
}

The problem is that `self.path` is immutable, so I can't pass it to the
inout parameter of `withUnsafePointer(to:)`, even though its value is never
modified. I can either (1) write the `description` accessor as `mutating
get` — but then it no longer conforms to `CustomStringConvertible` — or (2)
copy the `path` field into a local `var` that can be passed to
`withUnsafePointer(to:)`, and hope that the compiler can optimize away the
copy.

In the absence of the Swift 4 ownership model (with which I presume this
could be marked as a borrowed reference), is (2) the best I can do for
now? And is this the sort of issue that the Ownership Manifesto is
designed to address?

Thanks,

— Russell Finn


(Quinn “The Eskimo!”) #2

… is (2) the best I can do for now?

Yes.

btw You can simplify the code to this:

    var description: String {
        var tmp = self.path
        return String(cString: &tmp.0)
    }

but it still needs that copy.

And is this the sort of issue that the Ownership Manifesto is designed to address?

I’m not sufficiently up to speed on that topic to answer this. Sorry.

Share and Enjoy

···

On 22 Feb 2017, at 22:16, Russell Finn via swift-users <swift-users@swift.org> wrote:
--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware


(Martin R) #3

Is that simplified guaranteed to work? The Swift book says that

As an optimization, when the argument is a value stored at a physical address in memory, the same memory location is used both inside and outside the function body.

but also

Write your code using the model given by copy-in copy-out, without depending on the call-by-reference optimization, so that it behaves correctly with or without the optimization.

If `tmp.0` is copied to a separate location then `String(cString: &tmp.0)` would not work correctly, or would it?

Thank you in advance,
Martin

···

On 23 Feb 2017, at 09:33, Quinn The Eskimo! via swift-users <swift-users@swift.org> wrote:

On 22 Feb 2017, at 22:16, Russell Finn via swift-users <swift-users@swift.org> wrote:

… is (2) the best I can do for now?

Yes.

btw You can simplify the code to this:

   var description: String {
       var tmp = self.path
       return String(cString: &tmp.0)
   }

but it still needs that copy.

And is this the sort of issue that the Ownership Manifesto is designed to address?

I’m not sufficiently up to speed on that topic to answer this. Sorry.

Share and Enjoy
--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

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


(Quinn “The Eskimo!”) #4

Based on a previous conversation I had with the Swift folks about this, yes.

However, I don’t work on the compiler so it’s possible I misunderstood. Perhaps someone from SwiftLand™ will chime in here.

Share and Enjoy

···

On 23 Feb 2017, at 10:31, Martin R <martinr448@gmail.com> wrote:

Is that simplified guaranteed to work?

--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware


(Russell Finn) #5

Thanks to Joe and Quinn for their answers. I have a related followup — a
co-worker learning Swift wrote the following function:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            return NSUUID(uuidBytes: zerosPtr).uuidString
        }
    }

but was puzzled that Xcode 8.2.1 gave an error "Cannot convert value of
type 'UnsafePointer<_>' to expected argument type 'UnsafePointer<UInt8>!'"
on the line with the NSUUID initializer. Their expectation was that
`zerosPtr` would be of type `UnsafePointer<UInt8>` because `allZeros` is of
type `[UInt8]`.

They discovered that they could work around this by adding a call to
`withMemoryRebound`:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            zerosPtr.withMemoryRebound(to: UInt8.self, capacity:
allZeros.count) { zerosPtr in
                return NSUUID(uuidBytes: zerosPtr).uuidString
            }
        }
    }

but felt that this should be unnecessary. Perhaps I'm missing something
simple, but I was unable to explain this compiler behavior; can anyone on
the list do so?

(Yes, I did point out that they could pass `&allZeros` directly to
`NSUUID(uuidBytes:)`.)

Thanks — Russell


(Hooman Mehr) #6

Your co-worker needs to get passed the learning curve of these “unsafe” APIs and note that Swift arrays are complex data structures. &allZeros does not give you a pointer to a bunch of zero bytes, but a pointer to a struct that contains the private implementation details of allZeros array.

Here is the correct way to do it:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return allZeros.withUnsafeBufferPointer { NSUUID(uuidBytes: $0.baseAddress).uuidString }
}

···

On Mar 1, 2017, at 2:35 PM, Russell Finn via swift-users <swift-users@swift.org> wrote:

Thanks to Joe and Quinn for their answers. I have a related followup — a co-worker learning Swift wrote the following function:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            return NSUUID(uuidBytes: zerosPtr).uuidString
        }
    }

but was puzzled that Xcode 8.2.1 gave an error "Cannot convert value of type 'UnsafePointer<_>' to expected argument type 'UnsafePointer<UInt8>!'" on the line with the NSUUID initializer. Their expectation was that `zerosPtr` would be of type `UnsafePointer<UInt8>` because `allZeros` is of type `[UInt8]`.

They discovered that they could work around this by adding a call to `withMemoryRebound`:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            zerosPtr.withMemoryRebound(to: UInt8.self, capacity: allZeros.count) { zerosPtr in
                return NSUUID(uuidBytes: zerosPtr).uuidString
            }
        }
    }

but felt that this should be unnecessary. Perhaps I'm missing something simple, but I was unable to explain this compiler behavior; can anyone on the list do so?

(Yes, I did point out that they could pass `&allZeros` directly to `NSUUID(uuidBytes:)`.)

Thanks — Russell

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


(Russell Finn) #7

I agree that the use of `withUnsafeBufferPointer` to get to the contents of
an array makes more sense than `withUnsafePointer`, once you discover the
concept of a "buffer pointer" (which a new Swift programmer may not do at
first).

However, I note that the following code:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return NSUUID(uuidBytes: &allZeros)
    }

also compiles without error and returns the intended value. This appears to
be supported by the "Constant Pointers" section of the "Interacting with C
APIs" chapter of "Using Swift with Cocoa and Objective-C": "When a
function is declared as taking an `UnsafePointer<Type>` argument, it can
accept ... a `[Type]` value, which is passed as a pointer to the start of
the array."

I suspect the availability of `&array` in this context, coupled with an
awareness of the existence of `withUnsafePointer(to:)`, is what led my
co-worker down the wrong road (and to a puzzling error message); but since
we have now discovered two more appropriate ways to write the code in
question, perhaps it's not worth worrying about too much.

Thanks to everyone for the discussion.

···

On Wed, Mar 1, 2017 at 7:36 PM, Hooman Mehr <hooman@mac.com> wrote:

Your co-worker needs to get passed the learning curve of these “unsafe”
APIs and note that Swift arrays are complex data structures. &allZeros does
not give you a pointer to a bunch of zero bytes, but a pointer to a struct
that contains the private implementation details of allZeros array.

Here is the correct way to do it:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return allZeros.withUnsafeBufferPointer { NSUUID(uuidBytes: $0.
baseAddress).uuidString }
}

On Mar 1, 2017, at 2:35 PM, Russell Finn via swift-users < > swift-users@swift.org> wrote:

Thanks to Joe and Quinn for their answers. I have a related followup — a
co-worker learning Swift wrote the following function:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            return NSUUID(uuidBytes: zerosPtr).uuidString
        }
    }

but was puzzled that Xcode 8.2.1 gave an error "Cannot convert value of
type 'UnsafePointer<_>' to expected argument type 'UnsafePointer<UInt8>!'"
on the line with the NSUUID initializer. Their expectation was that
`zerosPtr` would be of type `UnsafePointer<UInt8>` because `allZeros` is of
type `[UInt8]`.

They discovered that they could work around this by adding a call to
`withMemoryRebound`:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            zerosPtr.withMemoryRebound(to: UInt8.self, capacity:
allZeros.count) { zerosPtr in
                return NSUUID(uuidBytes: zerosPtr).uuidString
            }
        }
    }

but felt that this should be unnecessary. Perhaps I'm missing something
simple, but I was unable to explain this compiler behavior; can anyone on
the list do so?

(Yes, I did point out that they could pass `&allZeros` directly to
`NSUUID(uuidBytes:)`.)

Thanks — Russell

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


(Hooman Mehr) #8

Yes, the easiest way is to rely on compiler magic for ObjC/C interoperability, but it is also important to know what is really happening.

My preferred version of the compiler magic is this actually:

func allZerosUUID() -> String {

    return NSUUID(uuidBytes: [UInt8](repeating: 0, count: 32)).uuidString
}

Which is effectively this:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return NSUUID(uuidBytes: allZeros).uuidString
}

(Note the use of let and absence of &).

···

On Mar 2, 2017, at 1:33 PM, Russell Finn <rsfinn@gmail.com> wrote:

I agree that the use of `withUnsafeBufferPointer` to get to the contents of an array makes more sense than `withUnsafePointer`, once you discover the concept of a "buffer pointer" (which a new Swift programmer may not do at first).

However, I note that the following code:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return NSUUID(uuidBytes: &allZeros)
    }

also compiles without error and returns the intended value. This appears to be supported by the "Constant Pointers" section of the "Interacting with C APIs" chapter of "Using Swift with Cocoa and Objective-C": "When a function is declared as taking an `UnsafePointer<Type>` argument, it can accept ... a `[Type]` value, which is passed as a pointer to the start of the array."

I suspect the availability of `&array` in this context, coupled with an awareness of the existence of `withUnsafePointer(to:)`, is what led my co-worker down the wrong road (and to a puzzling error message); but since we have now discovered two more appropriate ways to write the code in question, perhaps it's not worth worrying about too much.

Thanks to everyone for the discussion.

On Wed, Mar 1, 2017 at 7:36 PM, Hooman Mehr <hooman@mac.com <mailto:hooman@mac.com>> wrote:
Your co-worker needs to get passed the learning curve of these “unsafe” APIs and note that Swift arrays are complex data structures. &allZeros does not give you a pointer to a bunch of zero bytes, but a pointer to a struct that contains the private implementation details of allZeros array.

Here is the correct way to do it:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return allZeros.withUnsafeBufferPointer { NSUUID(uuidBytes: $0.baseAddress).uuidString }
}

On Mar 1, 2017, at 2:35 PM, Russell Finn via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:

Thanks to Joe and Quinn for their answers. I have a related followup — a co-worker learning Swift wrote the following function:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            return NSUUID(uuidBytes: zerosPtr).uuidString
        }
    }

but was puzzled that Xcode 8.2.1 gave an error "Cannot convert value of type 'UnsafePointer<_>' to expected argument type 'UnsafePointer<UInt8>!'" on the line with the NSUUID initializer. Their expectation was that `zerosPtr` would be of type `UnsafePointer<UInt8>` because `allZeros` is of type `[UInt8]`.

They discovered that they could work around this by adding a call to `withMemoryRebound`:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            zerosPtr.withMemoryRebound(to: UInt8.self, capacity: allZeros.count) { zerosPtr in
                return NSUUID(uuidBytes: zerosPtr).uuidString
            }
        }
    }

but felt that this should be unnecessary. Perhaps I'm missing something simple, but I was unable to explain this compiler behavior; can anyone on the list do so?

(Yes, I did point out that they could pass `&allZeros` directly to `NSUUID(uuidBytes:)`.)

Thanks — Russell

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


(Russell Finn) #9

Ahh ... which is exactly implied by the documentation I quoted, except that
I'm so used to doing it the other way (with `var` and `&`) that I failed to
interpret what the text was saying. Thanks for the tip.

···

On Thu, Mar 2, 2017 at 4:43 PM, Hooman Mehr <hooman@mac.com> wrote:

Yes, the easiest way is to rely on compiler magic for ObjC/C
interoperability, but it is also important to know what is really happening.

My preferred version of the compiler magic is this actually:

func allZerosUUID() -> String {

    return NSUUID(uuidBytes: [UInt8](repeating: 0, count: 32)).uuidString
}

Which is effectively this:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return NSUUID(uuidBytes: allZeros).uuidString
}

(Note the use of let and absence of &).

On Mar 2, 2017, at 1:33 PM, Russell Finn <rsfinn@gmail.com> wrote:

I agree that the use of `withUnsafeBufferPointer` to get to the contents
of an array makes more sense than `withUnsafePointer`, once you discover
the concept of a "buffer pointer" (which a new Swift programmer may not do
at first).

However, I note that the following code:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return NSUUID(uuidBytes: &allZeros)
    }

also compiles without error and returns the intended value. This appears
to be supported by the "Constant Pointers" section of the "Interacting with
C APIs" chapter of "Using Swift with Cocoa and Objective-C": "When a
function is declared as taking an `UnsafePointer<Type>` argument, it can
accept ... a `[Type]` value, which is passed as a pointer to the start of
the array."

I suspect the availability of `&array` in this context, coupled with an
awareness of the existence of `withUnsafePointer(to:)`, is what led my
co-worker down the wrong road (and to a puzzling error message); but since
we have now discovered two more appropriate ways to write the code in
question, perhaps it's not worth worrying about too much.

Thanks to everyone for the discussion.

On Wed, Mar 1, 2017 at 7:36 PM, Hooman Mehr <hooman@mac.com> wrote:

Your co-worker needs to get passed the learning curve of these “unsafe”
APIs and note that Swift arrays are complex data structures. &allZeros does
not give you a pointer to a bunch of zero bytes, but a pointer to a struct
that contains the private implementation details of allZeros array.

Here is the correct way to do it:

func allZerosUUID() -> String {

    let allZeros = [UInt8](repeating: 0, count: 32)

    return allZeros.withUnsafeBufferPointer { NSUUID(uuidBytes: $0.
baseAddress).uuidString }
}

On Mar 1, 2017, at 2:35 PM, Russell Finn via swift-users < >> swift-users@swift.org> wrote:

Thanks to Joe and Quinn for their answers. I have a related followup — a
co-worker learning Swift wrote the following function:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            return NSUUID(uuidBytes: zerosPtr).uuidString
        }
    }

but was puzzled that Xcode 8.2.1 gave an error "Cannot convert value of
type 'UnsafePointer<_>' to expected argument type 'UnsafePointer<UInt8>!'"
on the line with the NSUUID initializer. Their expectation was that
`zerosPtr` would be of type `UnsafePointer<UInt8>` because `allZeros` is of
type `[UInt8]`.

They discovered that they could work around this by adding a call to
`withMemoryRebound`:

    func allZerosUUID() -> String {
        var allZeros = [UInt8](repeating: 0, count: 32)
        return withUnsafePointer(to: &allZeros) { zerosPtr in
            zerosPtr.withMemoryRebound(to: UInt8.self, capacity:
allZeros.count) { zerosPtr in
                return NSUUID(uuidBytes: zerosPtr).uuidString
            }
        }
    }

but felt that this should be unnecessary. Perhaps I'm missing something
simple, but I was unable to explain this compiler behavior; can anyone on
the list do so?

(Yes, I did point out that they could pass `&allZeros` directly to
`NSUUID(uuidBytes:)`.)

Thanks — Russell

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