I’m fairly sure this is a difference in the underlying C function sprintf, due to a difference in how the types are defined. long is 32 bits on Windows and word-size (i.e. 64 bits on modern hardware) on macOS/Linux.
Absolutely correct. Although the implementation of String.init(format:_:) parses the format itself for cross-platform consistency, it’s still using sprintf to get formatted string buffer under the hood. In fact, there’s a comment talking exactly about %l could be 4 or 8 bytes on different platforms.
Thanks for the explanations about the implementation of String(format:).
It would be interesting for String to have a purely Swift implementation for formatting rather than being based on C sprintf and obtain identical behaviour on all platforms.
I understood that Foundation is in a rewriting phase with one of the goals is "no more wrapped C code".
No more wrapped C code. With a native Swift implementation of Foundation, the framework no longer pays conversion costs between C and Swift, resulting in faster performance. A Swift implementation, developed as a package, also makes it easier for Swift developers to inspect, understand, and contribute code.
Does this mean for String format with compatibility on all platforms will be implemented in the new Foundation?
Regardless of implementation, it would not be desirable for a C-style format string to behave differently using a Swift API than using sprintf on the same platform. Foundation does provide (as it should) formatting APIs that have consistent behavior across platforms, but String(format:_:) is intentionally not that.
Foundation does provide (as it should) formatting APIs that have
consistent behavior across platforms
FYI, there are two generations of those, the old school ‘formatter’ types and the new ‘format style’ types. The docs on the latter could be better (r. 76414485), so my go-to resource is Gosh Darn Format Style!.
Those APIs are focused on locale-aware formatting. Your specific example is formatting a UInt64 as hex, which isn’t well-served by the built-in mechanism. However, the format style APIs allow you to implement new formatters that fully integrate into the APIs. Pasted in below is a very simple example of this [1].
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] I’m actually surprised that no one has done this before, but maybe my Search Fu is not good enough to find it.
import Foundation
struct HexFormatStyle<Input>: FormatStyle where Input: BinaryInteger {
typealias FormatInput = Input
typealias FormatOutput = String
var padToWidth: Int = 0
func format(_ value: Input) -> String {
let base = String(value, radix: 16)
let pad = String(repeating: "0", count: max(0, padToWidth - base.count))
return "0x\(pad)\(base)"
}
}
extension FormatStyle {
static func hex<Input>(padToWidth: Int = 0) -> HexFormatStyle<Input>
where Self == HexFormatStyle<Input>
{
HexFormatStyle(padToWidth: padToWidth)
}
}
let v32: UInt32 = 2037740909
print("value: \(v32.formatted(.hex()))")
let v64: UInt64 = 3735928559
print("value: \(v64.formatted(.hex(padToWidth: 16)))")
I just tested your code on macOS and Windows. It compile fine on macOS but FormatStyle seems not available in Foundation on Windows. I got the following error:
main.swift:18:11: error: cannot find type 'FormatStyle' in scope
extension FormatStyle {
^~~~~~~~~~~
PS: eskimo it's always a pleasure to read your technical articles for so many years.Thanks.