[swift-users] Ugliness bridging Swift String to char *

Jordan Rose jordan_rose at apple.com
Wed Mar 1 20:21:21 CST 2017


> On Mar 1, 2017, at 14:23, Kenny Leung via swift-users <swift-users at swift.org> wrote:
> 
> Hi All.
> 
> Swift automatically bridges String to char * when calling C functions. For instance, strlen gets translated as:
> 
>     public func strlen(_ __s: UnsafePointer<Int8>!) -> UInt
> 
> I can call it from Swift like this:
> 
>     strlen("|")
> 
> I’m But, I’m working with a C struct containing a char *:
> 
>     public struct _PQprintOpt {
>         public var header: pqbool /* print output field headings and row count */
>         public var align: pqbool /* fill align the fields */
>         public var fieldSep: UnsafeMutablePointer<Int8>! /* field separator */
>         ...
>     }
>     public typealias PQprintOpt = _PQprintOpt
> 
> When I try to assign to fieldSep like this:
> 
>     opt.fieldSep = "|"
> 
> I get the error:
> 
>     Cannot assign value of type 'String' to type 'UnsafeMutablePointer<Int8>!'
>     
> I assume that the difference is that strlen declares const char * and fieldSep is simply char *, so strlen is non-mutable while fieldSep is mutable. Is this correct?
> 
> I currently have this ugly hack to get this to work:
> 
>     var opt :PQprintOpt = PQprintOpt()
>     guard let fieldSeparator = "|".cString(using: .utf8) else {
>         throw Errors.databaseConnectionError("Could not set field separator")
>     }
>     opt.fieldSep = UnsafeMutablePointer(mutating:fieldSeparator)
> 
> Is there a cleaner way this could work, or should this be considered a compiler bug?

Hey, Kenny. The const vs non-const part is important, since both the implicit conversion and cString(using:) are allowed to return a pointer to the internal data being used by the String, and modifying that would be breaking the rules (and could potentially cause a crash). In the case of 'fieldSep', it's unlikely that anyone is going to mutate the contents of the string; it was probably just the original author (you?) not bothering to be const-correct. If you control this struct, a better fix would be to use 'const char *' for the field.

That said, that's not the main isuse. The implicit conversion from String to UnsafePointer<CChar> is only valid when the string is used as a function argument, because the conversion might need to allocate temporary storage. In that case, it’s important to know when it’s safe to deallocate that storage. For a function call, that’s when the call returns, but for storing into a struct field it’s completely unbounded. So there’s no implicit conversion there.

If you’re willing to limit your use of the pointer value to a single block of code, you can use withCString <https://developer.apple.com/reference/swift/string/1538904-withcstring>:

myString.withCString {
  var opt :PQprintOpt = PQprintOpt()
  opt.fieldSep = UnsafeMutablePointer(mutating: $0)
  // use 'opt'
}

Note that it is illegal to persist the pointer value beyond the execution of withCString, because it might point to a temporary buffer.

Alternately, if you’re willing to call free() later, you can use the C function strdup:

opt.fieldSep = strdup(myString)
// use ‘opt’
free(opt.fieldSep)


Of course, all of this is overkill for a string literal. Perhaps when Swift gets conditional conformances, we could consider making UnsafePointer<CChar> conform to ExpressibleByStringLiteral. Meanwhile, you can use the type designed specifically for this purpose, StaticString <https://developer.apple.com/reference/swift/staticstring>:

let separator: StaticString = “|”
opt.fieldSep = UnsafeMutablePointer(mutating: separator.utf8start)

Note that you still have to do the init(mutating:) workaround for the fact that you’re not allowed to mutate this string.

> 
> Also, why is the conversion to Swift an IUO? NULL is a totally valid value for fieldSep.

You're welcome to mark that field as _Nullable on the C side, but without annotations Swift makes very few assumptions about pointers imported from C.

Hope that helps!
Jordan

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-users/attachments/20170301/d6b46c94/attachment.html>


More information about the swift-users mailing list