[Pitch] UUID v7, other improvements

I wonder if its worth having a UUIDProtocol that only cares if something is UUID-like, and specific versioned types (UUID.V4, UUID.V7… alternatively spelled as UUIDv7 or UUIDV7) that are explicit about their requirements especially when constructed from bytes, strings, or decoded data. UUID can remain as a "version-erased" type that doesn't care about the kind of UUID that is decoded, and future code can be generic over UUIDProtocol if needed.

Edit: I think we can even do UUID.Protocol now to keep everything nice and namespaced :face_with_tongue:

2 Likes

some UUIDmight be pretty nice.

Most of our API designs end up with discussion about where we should be on the spectrum of "completely type safe and checked at compile time" vs "totally not type safe and nothing is guaranteed until run time". Usually, the former tends to be harder or at least more complicated to write in source, and sometimes has the side effect of banning totally valid code. The latter tends to result in surprises when you least expect them, like in production.

Everyone, naturally, has a different opinion on where the correct place is on this line. Plus, it will of course vary from API to API.

For me, for this API, I think having a concrete UUID type as the foundational API is sufficiently type safe. For the (imo, relatively rare) cases where some API absolutely demands type safety of the version for some reason, it would be trivial to wrap this primitive type. The harder part of generating the correct bytes is done as part of this proposal.

In other news, I've updated the proposal to move from Span<UInt8> to RawSpan due to discovering some alignment issues with InlineArray<UInt8>. I'll go ahead and mark the PR as ready for review.

11 Likes

The thing is it would be an error to insert a version 4 UUID into a dataset of version 7 UUIDs, and I've been convinced by the arguments here that a type check to prevent such a mistake would be extremely valuable, especially when the mistake is as easy as typing .init() when creating a new value. It would be a shame to get all these improvements only to see the same wrapper in every project that cared to make sure the correct UUID type is being used.

The correct UUID version is only required when generating a UUID or extracting the timestamp. However, RFC 9562 rightly discourages timestamp extraction except in cases of extreme necessity. Mixing UUIDs of different versions is not only acceptable but also desirable for migrating from UUIDv4 to UUIDv7 without cascading key updates.

3 Likes

I think UUIDv8 is going to have even stronger desires to want extraction, even if the extraction is going to be different for every use case. That said, I think Foundation.UUID is very definitively "any UUID" even before this proposal, even if its default init is "new v4 UUID", and if we want strongly-typed specific UUIDs, the new API being proposed would still be valid on the existing UUID struct type.

3 Likes

Thanks for pitching this. I am very much in favor of adding this, and there was some great discussion already during the thread. Overall, I would like to see supported added for all UUID versions from 1 through 8. There are valid use-cases for each of them in the wild, and if we don't support a certain version, it just means developers are to build the API themselves. We have gone through a similar experience in swift-crypto where we initially didn't offer support for RSA due to its security implications, but the reality is that there are many systems still using it, and we need Swift applications to interoperate with them.

I also did a quick skim over the latest version of the proposal in the PR and noted two smaller comments.

case reserved

It feels odd to add this case. It might be that in future versions of the UUID RFC this gets defined and gets an actual name, right?

    /// For version 7 UUIDs, returns the `Date` encoded in the most significant 48 bits. Returns `nil` for all other versions.
    ///
    /// The returned date has millisecond precision, as specified by RFC 9562.
    ///
    /// - Note: Even though this implementation, or others, may choose to encode more precision into other bytes of the `UUID`, this method may only return the portion of the timestamp stored in the RFC-specified bytes.
    public var date: Date? {
        get
    }

Version 1, 6 and 7 all contain a timestamp any reason why this only returns a valid date for version 7?

RFC 9562 does not officially mark any UUID version as deprecated or obsolete. However, the authors note that versions 1-5 have flaws and are not recommended for new systems (de facto deprecated). Officially declaring them obsolete would break existing implementations, so they compromised: all versions are included in the standard, but versions 6–8 are clearly preferred - especially UUIDv7.

Do they? I'm seeing "prefer v5 over v3" and "prefer v7 over v6" and a very soft "v6 is an improvement over v1", but nothing saying v4 or v5 are bad, only that if you don't like SHA-1 you should use v8 instead of pretending some other hash is v5. (And v2 UUIDs are "outside the scope of this specification".)

1 Like

Maybe a more structured API for this? Something like:

switch uuid.params {
    case .v7(let date):

You have read, though not thoroughly, what ultimately made it into the standard, with the authors taking "political correctness" into account. I'm telling you about what's going on behind the scenes. You can see the truth of my words for yourself by checking out the standard's huge GitHub repository: GitHub - uuid6/uuid6-ietf-draft: Next Generation UUID Formats · GitHub

No one should be attempting to implement a standard by reading “between the lines”. If the RFC can’t be taken at face value, it’s worthless.

5 Likes

The fact that UUIDs can be used to create unique, reasonably short values in distributed systems without requiring coordination makes them a good alternative, but UUID versions 1-5, which were originally defined by [RFC4122], lack certain other desirable characteristics...

See the source: RFC 9562 - Universally Unique IDentifiers (UUIDs)

The standard is certainly useful, but it's not perfect. Some aspects were left out for non-technical reasons or due to a lack of time to develop a good compromise.

It's not perfect, no. Besides it's not even a single standard: the pitch itself has to refer to the (now obsolete!) RFC 4122 IRT lowercasing, plus there's ITU-T X.667.

The efforts of enthusiasts wouldn't have been enough to push the standard through two separate bureaucratic registration bodies. And compared to other attempts to create a database-friendly unique identifier, UUIDv7 is practically ideal.

I'm not really fundamentally opposed to more versions, but I do want to limit the scope at some point so we can actually implement and ship these improvements in the near term.

5 Likes

Thanks for clarifying this. I just wanted to make sure we are open to adding support for the other versions as well since there will always be systems around that require them.

1 Like

Hi, I would need UUIDs v7 in a new project. Is that already implemented by now (and I missed it) or is that another proposal stuck in the pitch/bikeshedding phase?

Kind regards,

Lars

This is accepted in the review thread! I defer to @Tony_Parker to comment when the implementation will be ready

1 Like

There's a third party package that generates UUIDv7, if you don't want to wait for Foundation support: https://swiftpackageindex.com/mhayes853/swift-uuidv7

1 Like