Convert String to UnsafeMutablePointer<Int8>

(David Hart) #1

How can I convert a String to a UnsafeMutablePointer<Int8> for passing to a non-const C API?

(Jordan Rose) #2

Does the C API actually mutate the buffer? If so, I don't think there's currently a safe way to do this that doesn't require copying:

var buffer = Array(str.utf8)
buffer.append(0)
importedCFunction(&buffer)
let newString = String(cString: buffer)

If the C API doesn't mutate the buffer and is just declared wrong suboptimally, you can reinterpret the pointer and cross your fingers:

str.withCString {
  importedCFunction(UnsafeMutablePointer(mutating: $0))
}
2 Likes
(David Hart) #3

Thanks! I’m surprised about the first answer. I was hoping there was a cStringCopy function. Would that be an inteeesting addition to the Standard Library?

(Ben Cohen) #4

I don't know if there's enough call for such an API to justify adding it.

I think the reason @jrose questioned whether the API truly mutated the buffer is that actually-mutating C string APIs are unusual, because they're a bit weird. With Unicode strings, almost any mutation ends up needing to resize the string, which introduces all sorts of complications (you need to pass before/after lengths and capacities back and forth). Giving mutating access to a String's underlying buffer could also break invariants or perf flags, like normalization or ascii-ness, that the String might be preserving.

1 Like
(Jordan Rose) #5

Also, if you admit to working in C-string-land, strdup is perfectly callable from Swift. But then you have to remember to free the result yourself, which is why I used Array instead.

(Ben Cohen) #6

Array also has the future potential of being convertible back to a String type at no cost, via some of the ideas outlined in this post. This would mitigate some of the performance downsides of operating "outside" of the string type.

1 Like
(David Hart) #7

@jrose By the way, what if I know the API won’t mutate the String but the closure based cString doesn’t work for me, is there another way not to incure a copy?

(Ben Cohen) #8

What's the meaning of "doesn't work for me" here?

(David Hart) #9

The C API I’m trying to call looks like this:

struct {
    int id;
    const char* name;
} foo_data;

void foo(const foo_data* array_of_datas);

I have a Swift Data type:

struct Data {
  private let id: Int
  private let name: String
}

let datas: [Data]

The reason I can’t use the closure based API is that I can’t nest arbitrarily long arrays of closure for each String:

datas[0].name.usingCString { s0 in
    let d0 = foo_data(id: datas[0].id, name: s0)
    datas[1].name.usingCString { s1 in
        let d1 = foo_data(id: datas[1], name: s1)
        // ...
    }
}
(Jordan Rose) #10

I don't think there's a great answer for this. It's similar to the problem discussed in "Swift and C++ memory question", and the best answer I came to required manual allocation.

(David Hart) #11

Stupid question, but shouldn't Swift warn when passing a pointer to a C struct initializer using the ampersand syntax? As it can't be guaranteed to be valid after the initializer call, we've got a problem waiting to happen. That's what happened to me, by the way:

struct {
    const char* SomeCStruct
} SomeCStruct;

let a = SomeCStruct(name: &string) // warn: the pointer to 'string' shouldn't be stored as it can't be guaranteed to be valid after the call
print(a.name) // This is already potentially invalid, correct?
1 Like
(Jordan Rose) #12

Right now the compiler doesn't treat C struct initializers as special in any way, but that conceptual purity probably isn't worth the bugs it lets people introduce.

(Quinn “The Eskimo!”) #13

the best answer I came to required manual allocation.

The downside to that is that it generates a lot of allocator traffic. That may or may not be a problem in practice, depending on a bunch of factors (How large are these arrays? Where do they originate from? How often do they cross the Swift / C boundary?).

If allocator traffic is a problem then my next step would be to abandon [FooData] [1] on the Swift side of things and replace it with something like this:

struct FooDataPack {
    var datas: [FooData] { get }
}

This type could then take responsibility for bridging the entire array over to C, and the fact that it’s a separate type would give it the opportunity to optimise that bridging based on the usage in your specific environment. For example, if the name strings are most commonly used on the C side, it could store UnsafePointer<CChar> under the covers and only generate a Swift String on demand.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

[1] I’ve renamed your Data to FooData because… well… Data is just too confusing given the very commonly-used Foundation type of the same name.