I think it's generally bad form to use property syntax for anything that the caller can't treat as "behaves as if it were a stored property, even though it probably isn't", just as mental model thing. I personally think even Date.now goes too far, but you could still think of it as a stored property that is just updated faster than you could observe—it is a "property" of the current world in a natural-language sense, that two different observers could see and agree on. With randomness involved, though, while "updating at blurring speeds" is technically still a mental model that works, it's not a helpful one—it would be simpler to understand v4 and v7 UUID creation as doing arbitrary computation than as accessing some abstract state.
As a bonus, ideally they would take RandomNumberGenerators as a defaulted parameter, and then it's just the normal base case for "all parameters defaulted".
Note that this convention isn't true in every language; Ruby, for one, was famous for preferring no parens on a method call taking no arguments.
I don't agree. The UUID type as currently documented does not make any assumptions about which versions are allowed to be represented, so any libraries using it can't do this either unless they add documentation e.g. a precondition. The only time any version number of the spec is mentioned at all is the parameterless constructor.
Receiving any UUID as user input requires a certain level of trust that it was generated according to the spec. Reading only the version number isn't enough to establish trust after the fact.
Not sure I understand your point here. We're not talking about what's documented now, we're talking about what will be documented with the new support, so we can certainly document a guarantee there. And UUID()is documented as v4.
And I'm not sure who's talking about receiving a UUID as user input, that would essentially never happen. You either get one from the backend or generate one locally.
Do you have any scenarios where a developer would intentionally mix UUID versions in the same value?
Yes, UUID.init the initialiser is documented to return v4, but UUID the type isn't guaranteed to hold any particular version.
While it's probably reasonable to stick to one version in application code and encode that in the type system, I'm not convinced Foundation is the place to add that level of type safety, as it's easy enough to define a wrapper type with that guarantee around UUID in user code. Same as for URL, which allows a far wider variety of URL types.
Foundation would be the only place to add that safety, if we wanted it, since it vends the type(s) in question.
Aside from the "I need a random identifier, don't care which" case, what's the use case for a unversioned UUID? Especially when supporting multiple versions?
(And yes, URL is awful in this regard too, but that's not what we're talking about here.)
I don’t think I ever cared about the UUID version that my code gets from a file, database, network, etc. I just store it in an UUID and use that value for equity checking/hashing/identifying. The version of the UUID plays no role in any of those operations.
For creation, I prefer the static functions v4(), v7(), etc.
Yeah, to the extent I've worked with any sort of more-strictly-typed UUID it's always been about "what sort of record does this identify" rather than "what specific UUID variant is this".
Right, creation is where the version matters, but there's no way to ensure that creation is correct unless we can enforce it in some way. In Swift that's typically the type system. For client operations, where you may never create them at all, aside from testing, it probably doesn't matter. For server applications it matters a lot, where you have to use a particular version all the time. Read / write from a db, caching, client request / response, all need you to be consistent.
Do you have any scenarios where a developer would intentionally mix UUID versions in the same value?
Any server infrastructure where you can't guarantee that every server component can be updated/deployed at the exact same time, which is most of them. If one component generates V4 UUIDs and another component accepts nothing but V4 UUIDs, it's no longer possible to upgrade the first component to generate V6 UUIDs without breaking compatibility. Updating the second component to accept both V4 and V6 is now much more difficult without a version-agnostic UUID type.
For server applications it matters a lot, where you have to use a particular version all the time. Read / write from a db, caching, client request / response, all need you to be consistent.
I have never encountered a single project that requires exact UUID versioning. At best, I've seen older UUID versions get marked as deprecated, but never seen newer versions get rejected because the system somehow can't handle them.
That's exactly why typed UUIDs would be valuable, so I hardly see it as a downside. And of course, we'd still have UUID, so if you don't care, you can always just parse the bytes. But in your example, even using UUID itself could be too much typing, as you could have components switch between UUID ids and stringified Int ids (yes, I've had this happen), in which case anyone not treating it as a simple String would have to update.
So really I don't see "what if I don't care?" as an argument against typing, as you can simply continue not caring, to the degree that you care about.
I mean, there are known situations where you have to care, like generating values for certain databases, so there's certainly uses for specific creation methods, otherwise this proposal is pretty pointless.
Unless you're arguing Swift / Foundation shouldn't be used for situations where particulars types of UUIDs are required, typed versions should at least be considered. At the least, ensuring types can be added on top of UUID without overhead should be a guarantee.
I encourage you to follow the RFC 9562, like it is perfectly implemented in PostgreSQL 18.
When generating UUIDs, the version is crucial. Therefore, PostgreSQL 18 includes uuidv7() function for modern systems and uuidv4() for legacy systems that cannot be patched. There is no "default version" for lazy programmers.
UUIDs of any versions, including UUIDv1 and UUIDv6 obtained from external sources, are stored in the same UUID column. The same column can simultaneously store both new UUIDv7s and old UUIDv4s. There is no need to replace old UUIDv4s with new UUIDv7s, which would require cascading key replacements in the database. As new UUIDv7s are added, database performance will improve. All stored UUIDs are treated as opaque identifiers, not subject to parsing or even version number determination unless absolutely necessary.
If a legacy system only accepts UUIDs of specific versions, it will have to relax its version number enforcement, as DataGrip has already done.
This statement is uncalled for, and is not relevant to updating Foundation’s UUID type. Apple is not going to create a source break by removing the default initializer.
There's been a lot of great discussion in this thread about alternatives for initializers. I've attempted to distill it into the alternatives considered section of the updated proposal. For now I'd like for the proposal to stick with UUID.version4(), UUID.version7(), and UUID.version7( ... args ...).
Also:
Added version setter
Added variant enum, and getter
Added additional alternatives considered for discussion points raised here
This feels like an odd one, especially since neither date nor variant have setters. What’s the intended use case for changing the version but leaving the rest of the bits unchanged?
I thought about this and decided to add the date/offset arguments (nullable, nil by default) to the version7() API. It doesn't change the common case of passing no arguments, but makes using it without specifying a random number generator easier - seems worth it.
It's more for use with the OutputSpan initializer where you fill up all the other bits. This makes it easier to correctly comply with the spec to, say, put version 8 in there, after filling everything else up.
In the event that someone constructs their own UUID by setting each byte, it seems better to let them fill the version field by setting the relevant bits instead of using a version property, since it's already the responsibility of the caller to ensure all other bits are set according to the spec. All other usages of the version setter seem like opportunities for a footgun.
Yes. .timeOrdered and .random would be for developers who don’t care about UUID specifications. I would suggest that the current implementation remain available as .v4 until deprecated; likewise any other UUID version that is implemented. Implementing every version of UUID just to have the complete set goes way beyond the current proposal so I think we’re only talking about .v4 and .v7 at this time.