algal algal - 8 months ago 60
Swift Question

Passing Swift strings as C strings in variable argument lists

I am trying to use the old printf-style string formatters to define a new Swift string from an old Swift string.

I notice this works fine as long as I am starting with a Swift string literal, but not a string variable.

// OK!
String(format:"%s", "hello world".cStringUsingEncoding(NSUTF8StringEncoding))

// ERROR: argument type '[CChar]?' does not conform to expected type 'CVarArgType'
let s = "hello world"
String(format:"%s", s.cStringUsingEncoding(NSUTF8StringEncoding))

Why does this happen?

And what's the best workaround?

(Please note that I am aware of but do not want to use the Cocoa string formatter
. I want the printf-style formatting code because what I'm actually trying to do is get quick and dirty tabular alignment with codes like

This question concerns Swift 2.2.


Don't create a C string just for alignment. There is a method stringByPaddingToLength for that.

// Swift 2
s.stringByPaddingToLength(10, withString: " ", startingAtIndex: 0)

// Swift 3
s.padding(toLength: 10, withPad: " ", startingAt: 0)

Note that it will truncate the string if it is longer than 10 UTF-16 code units.

The problem here is, there are two methods in Swift named cStringUsingEncoding:

  1. func cStringUsingEncoding(encoding: UInt) -> UnsafePointer<Int8>, as a method of NSString
  2. func cStringUsingEncoding(encoding: NSStringEncoding) -> [CChar]?, as an extension of String

If we want a pointer, we need to ensure we are using an NSString, not a String.

In the first example, a string literal can be a String or NSString, so the compiler chooses NSString since the other one won't work.

But in the second example, the type of s is already set to String, so the method that returns [CChar]? is chosen.

This could be worked-around by forcing s to be an NSString:

let s: NSString = "hello world"
String(format:"%s", s.cStringUsingEncoding(NSUTF8StringEncoding))