|
| 1 | +# Expose attosecond representation of `Duration` |
| 2 | + |
| 3 | +* Proposal: [SE-0457](0457-duration-attosecond-represenation.md) |
| 4 | +* Authors: [Philipp Gabriel](https://github.com/ph1ps) |
| 5 | +* Review Manager: [Stephen Canon](https://github.com/stephentyrone) |
| 6 | +* Status: **Active review (January 16 ... 30, 2025)** |
| 7 | +* Implementation: [swiftlang/swift#78202](https://github.com/swiftlang/swift/pull/78202) |
| 8 | +* Review: ([pitch](https://forums.swift.org/t/pitch-adding-int128-support-to-duration)) |
| 9 | + |
| 10 | +## Introduction |
| 11 | +This proposal introduces public APIs to enable seamless integration of `Int128` into the `Duration` type. Specifically, it provides support for directly accessing a `Duration`'s attosecond representation via the newly available `Int128` type and simplifies the creation of `Duration` values from attoseconds. |
| 12 | + |
| 13 | +## Motivation |
| 14 | +The `Duration` type currently offers two ways to construct and decompose itself: |
| 15 | + |
| 16 | +**Low and high bits** |
| 17 | +```swift |
| 18 | +public struct Duration: Sendable { |
| 19 | + public var _low: UInt64 |
| 20 | + public var _high: Int64 |
| 21 | + public init(_high: Int64, low: UInt64) { ... } |
| 22 | +} |
| 23 | +``` |
| 24 | +**Components** |
| 25 | +```swift |
| 26 | +extension Duration { |
| 27 | + public var components: (seconds: Int64, attoseconds: Int64) { ... } |
| 28 | + public init(secondsComponent: Int64, attosecondsComponent: Int64) { ... } |
| 29 | +} |
| 30 | +``` |
| 31 | +However, both approaches have limitations when it comes to exposing `Duration`'s total attosecond representation: |
| 32 | +- The `_low` and `_high` properties are underscored, indicating that their direct use is discouraged. |
| 33 | +- The `components` property decomposes the value into seconds and attoseconds, requiring additional arithmetic operations for many use cases. |
| 34 | + |
| 35 | +This gap becomes particularly evident in scenarios like generating a random `Duration`, which currently requires verbose and potentially inefficient code: |
| 36 | +```swift |
| 37 | +func randomDuration(upTo maxDuration: Duration) -> Duration { |
| 38 | + let attosecondsPerSecond: Int128 = 1_000_000_000_000_000_000 |
| 39 | + let upperRange = Int128(maxDuration.components.seconds) * attosecondsPerSecond + Int128(maxDuration.components.attoseconds) |
| 40 | + let (seconds, attoseconds) = Int128.random(in: 0..<upperRange).quotientAndRemainder(dividingBy: attosecondsPerSecond) |
| 41 | + return .init(secondsComponent: Int64(seconds), attosecondsComponent: Int64(attoseconds)) |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +By introducing direct `Int128` support to `Duration`, this complexity is eliminated. Developers can write concise and efficient code instead: |
| 46 | +```swift |
| 47 | +func randomDuration(upTo maxDuration: Duration) -> Duration { |
| 48 | + return Duration(attoseconds: Int128.random(in: 0..<maxDuration.attoseconds)) |
| 49 | +} |
| 50 | +``` |
| 51 | +This addition reduces boilerplate, minimizes potential errors, and improves performance for use cases requiring high-precision time calculations. |
| 52 | + |
| 53 | +## Proposed solution |
| 54 | +This proposal complements the existing construction and decomposition options by introducing a third approach, leveraging the new `Int128` type: |
| 55 | + |
| 56 | +- A new computed property `attoseconds`, which exposes the total attoseconds of a `Duration` as an `Int128`. |
| 57 | +- A new initializer `init(attoseconds: Int128)`, which allows creating a `Duration` directly from a single 128-bit value. |
| 58 | + |
| 59 | +These additions provide a direct and efficient mechanism for working with `Duration` values while maintaining full compatibility with existing APIs. |
| 60 | + |
| 61 | +## Detailed design |
| 62 | +Internally, the `Duration` type represents its value using the underscored `_high` and `_low` properties, which encode attoseconds as a 128-bit integer split into two 64-bit values. The proposed APIs unify these components into a single `Int128` representation: |
| 63 | +```swift |
| 64 | +@available(SwiftStdlib 6.0, *) |
| 65 | +extension Duration { |
| 66 | + /// The duration represented in attoseconds. |
| 67 | + public var attoseconds: Int128 { |
| 68 | + Int128(_low: _low, _high: _high) |
| 69 | + } |
| 70 | + |
| 71 | + /// Initializes a `Duration` from the given number of attoseconds. |
| 72 | + public init(attoseconds: Int128) { |
| 73 | + self.init(_high: attoseconds._high, low: attoseconds._low) |
| 74 | + } |
| 75 | +} |
| 76 | +``` |
| 77 | + |
| 78 | +## Source compatibility |
| 79 | +This proposal is additive and source-compatible with existing code. |
| 80 | + |
| 81 | +## ABI compatibility |
| 82 | +This proposal is additive and ABI-compatible with existing code. |
| 83 | + |
| 84 | +## Implications on adoption |
| 85 | +The additions described in this proposal require a new version of the standard library. |
| 86 | + |
| 87 | +## Alternatives considered |
| 88 | +### Static factory instead of or in addtion to initializer |
| 89 | +An alternative approach to the proposed `init(attoseconds:)` initializer is a static factory method. This design aligns with existing methods like `nanoseconds`, `microseconds`, etc., and provides a consistent naming pattern for creating `Duration` values. |
| 90 | + |
| 91 | +```swift |
| 92 | +@available(SwiftStdlib 6.0, *) |
| 93 | +extension Duration { |
| 94 | + public static func attoseconds(_ attoseconds: Int128) -> Duration { ... } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +However, this approach would introduce asymmetry to other factory methods which support both `Double` and `BinaryInteger` overloads: |
| 99 | +```swift |
| 100 | +extension Duration { |
| 101 | + public static func microseconds<T: BinaryInteger>(_ microseconds: T) -> Duration { ... } |
| 102 | + public static func microseconds(_ microseconds: Double) -> Duration { ... } |
| 103 | +} |
| 104 | +``` |
| 105 | +For attoseconds, adding these overloads would lead to practical issues: |
| 106 | + |
| 107 | +1. A `Double` overload is nonsensical because sub-attoseconds are not supported, meaning the method cannot represent fractional attoseconds. |
| 108 | +2. A `BinaryInteger` overload introduces additional complexity. Since it would need to support types other than `Int128`, arithmetic operations would be necessary to ensure correct scaling and truncation, negating the simplicity and precision that the `Int128`-specific initializer aims to provide. |
| 109 | + |
| 110 | +Ultimately, the static func `attoseconds(_:)` would likely end up as a one-off method with only an `Int128` overload. This inconsistency diminishes the appeal of the factory method approach. The proposed `init(attoseconds:)` initializer avoids these issues, offering a direct and clear way to work with attoseconds, while remaining symmetrical with the existing `Duration` API structure. |
0 commit comments