String(format:) behaves differently on Windows

It looks like the 64-bit format is truncated to 32-bit on Windows.

// macOS
let u64 = 0x0123456789ABCDEF
print(String(format: "0x%016lX", u64))  // 0x0123456789ABCDEF
// Windows
let u64 = 0x0123456789ABCDEF
print(String(format: "0x%016lX", u64))  // 0x0000000089ABCDEF

To achieve same result on Windows I must use "0x%016llX" format.
However I expect to get the same display on all platforms using the same format.

Thanks for considering a fix.

1 Like

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.

1 Like

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.

3 Likes

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)))")
4 Likes

Thanks Quinn, FormatStyle is very powerful !

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.

I believe that the Formatters API was never implemented in swift-corelibs-foundation and as such would not be available on non-Darwin platforms.