newtype_uuid/lib.rs
1//! A newtype wrapper around [`Uuid`].
2//!
3//! # Motivation
4//!
5//! Many large systems use UUIDs as unique identifiers for various entities. However, the [`Uuid`]
6//! type does not carry information about the kind of entity it identifies, which can lead to mixing
7//! up different types of UUIDs at runtime.
8//!
9//! This crate provides a wrapper type around [`Uuid`] that allows you to specify the kind of entity
10//! the UUID identifies.
11//!
12//! # Example
13//!
14//! ```rust
15//! use newtype_uuid::{GenericUuid, TypedUuid, TypedUuidKind, TypedUuidTag};
16//!
17//! // First, define a type that represents the kind of UUID this is.
18//! enum MyKind {}
19//!
20//! impl TypedUuidKind for MyKind {
21//! fn tag() -> TypedUuidTag {
22//! // Tags are required to be ASCII identifiers, with underscores
23//! // and dashes also supported. The validity of a tag can be checked
24//! // at compile time by assigning it to a const, like so:
25//! const TAG: TypedUuidTag = TypedUuidTag::new("my_kind");
26//! TAG
27//! }
28//! }
29//!
30//! // Now, a UUID can be created with this kind.
31//! let uuid: TypedUuid<MyKind> = "dffc3068-1cd6-47d5-b2f3-636b41b07084".parse().unwrap();
32//!
33//! // The Display (and therefore ToString) impls still show the same value.
34//! assert_eq!(uuid.to_string(), "dffc3068-1cd6-47d5-b2f3-636b41b07084");
35//!
36//! // The Debug impl will show the tag as well.
37//! assert_eq!(format!("{:?}", uuid), "dffc3068-1cd6-47d5-b2f3-636b41b07084 (my_kind)");
38//! ```
39//!
40//! If you have a large number of UUID kinds, consider defining a macro for your purposes. An
41//! example macro:
42//!
43//! ```rust
44//! # use newtype_uuid::{TypedUuidKind, TypedUuidTag};
45//! macro_rules! impl_typed_uuid_kind {
46//! ($($kind:ident => $tag:literal),* $(,)?) => {
47//! $(
48//! pub enum $kind {}
49//!
50//! impl TypedUuidKind for $kind {
51//! #[inline]
52//! fn tag() -> TypedUuidTag {
53//! const TAG: TypedUuidTag = TypedUuidTag::new($tag);
54//! TAG
55//! }
56//! }
57//! )*
58//! };
59//! }
60//!
61//! // Invoke this macro with:
62//! impl_typed_uuid_kind! {
63//! Kind1 => "kind1",
64//! Kind2 => "kind2",
65//! }
66//! ```
67//!
68//! # Implementations
69//!
70//! In general, [`TypedUuid`] uses the same wire and serialization formats as [`Uuid`]. This means
71//! that persistent representations of [`TypedUuid`] are the same as [`Uuid`]; [`TypedUuid`] is
72//! intended to be helpful within Rust code, not across serialization boundaries.
73//!
74//! - The `Display` and `FromStr` impls are forwarded to the underlying [`Uuid`].
75//! - If the `serde` feature is enabled, `TypedUuid` will serialize and deserialize using the same
76//! format as [`Uuid`].
77//! - If the `schemars08` feature is enabled, [`TypedUuid`] will implement `JsonSchema` if the
78//! corresponding [`TypedUuidKind`] implements `JsonSchema`.
79//!
80//! To abstract over typed and untyped UUIDs, the [`GenericUuid`] trait is provided. This trait also
81//! permits conversions between typed and untyped UUIDs.
82//!
83//! # Dependencies
84//!
85//! - The only required dependency is the [`uuid`] crate. Optional features may add further
86//! dependencies.
87//!
88//! # Features
89//!
90//! - `default`: Enables default features in the newtype-uuid crate.
91//! - `std`: Enables the use of the standard library. *Enabled by default.*
92//! - `serde`: Enables serialization and deserialization support via Serde. *Not enabled by
93//! default.*
94//! - `v4`: Enables the `new_v4` method for generating UUIDs. *Not enabled by default.*
95//! - `schemars08`: Enables support for generating JSON schemas via schemars 0.8. *Not enabled by
96//! default.* Note that the format of the generated schema is **not currently part** of the stable
97//! API, though we hope to stabilize it in the future.
98//! - `proptest1`: Enables support for generating `proptest::Arbitrary` instances of UUIDs. *Not enabled by default.*
99//!
100//! # Minimum supported Rust version (MSRV)
101//!
102//! The MSRV of this crate is **Rust 1.67.** In general, this crate will follow the MSRV of the
103//! underlying `uuid` crate or of dependencies, with an aim to be conservative.
104//!
105//! Within the 1.x series, MSRV updates will be accompanied by a minor version bump. The MSRVs for
106//! each minor version are:
107//!
108//! * Version **1.0.x**: Rust 1.60.
109//! * Version **1.1.x**: Rust 1.61. This permits `TypedUuid<T>` to have `const fn` methods.
110//! * Version **1.2.x**: Rust 1.67, required by some dependency updates.
111//!
112//! # Alternatives
113//!
114//! - [`typed-uuid`](https://crates.io/crates/typed-uuid): generally similar, but with a few design
115//! decisions that are different.
116
117#![forbid(unsafe_code)]
118#![warn(missing_docs)]
119#![cfg_attr(not(feature = "std"), no_std)]
120#![cfg_attr(doc_cfg, feature(doc_cfg, doc_auto_cfg))]
121
122use core::{
123 cmp::Ordering,
124 fmt,
125 hash::{Hash, Hasher},
126 marker::PhantomData,
127 str::FromStr,
128};
129use uuid::{Uuid, Version};
130
131/// A UUID with type-level information about what it's used for.
132///
133/// For more, see [the library documentation](crate).
134#[repr(transparent)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136#[cfg_attr(feature = "serde", serde(transparent, bound = ""))]
137pub struct TypedUuid<T: TypedUuidKind> {
138 uuid: Uuid,
139 _phantom: PhantomData<T>,
140}
141
142impl<T: TypedUuidKind> TypedUuid<T> {
143 /// The 'nil UUID' (all zeros).
144 ///
145 /// The nil UUID is a special form of UUID that is specified to have all
146 /// 128 bits set to zero.
147 ///
148 /// # References
149 ///
150 /// * [Nil UUID in RFC4122](https://tools.ietf.org/html/rfc4122.html#section-4.1.7)
151 #[inline]
152 #[must_use]
153 pub const fn nil() -> Self {
154 Self {
155 uuid: Uuid::nil(),
156 _phantom: PhantomData,
157 }
158 }
159
160 /// The 'max UUID' (all ones).
161 ///
162 /// The max UUID is a special form of UUID that is specified to have all
163 /// 128 bits set to one.
164 ///
165 /// # References
166 ///
167 /// * [Max UUID in Draft RFC: New UUID Formats, Version 4](https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format-04#section-5.4)
168 #[inline]
169 #[must_use]
170 pub const fn max() -> Self {
171 Self {
172 uuid: Uuid::max(),
173 _phantom: PhantomData,
174 }
175 }
176
177 /// Creates a UUID from four field values.
178 #[inline]
179 #[must_use]
180 pub const fn from_fields(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
181 Self {
182 uuid: Uuid::from_fields(d1, d2, d3, &d4),
183 _phantom: PhantomData,
184 }
185 }
186
187 /// Creates a UUID from four field values in little-endian order.
188 ///
189 /// The bytes in the `d1`, `d2` and `d3` fields will be flipped to convert into big-endian
190 /// order. This is based on the endianness of the UUID, rather than the target environment so
191 /// bytes will be flipped on both big and little endian machines.
192 #[inline]
193 #[must_use]
194 pub const fn from_fields_le(d1: u32, d2: u16, d3: u16, d4: [u8; 8]) -> Self {
195 Self {
196 uuid: Uuid::from_fields_le(d1, d2, d3, &d4),
197 _phantom: PhantomData,
198 }
199 }
200
201 /// Creates a UUID from a 128bit value.
202 #[inline]
203 #[must_use]
204 pub const fn from_u128(value: u128) -> Self {
205 Self {
206 uuid: Uuid::from_u128(value),
207 _phantom: PhantomData,
208 }
209 }
210
211 /// Creates a UUID from a 128bit value in little-endian order.
212 ///
213 /// The entire value will be flipped to convert into big-endian order. This is based on the
214 /// endianness of the UUID, rather than the target environment so bytes will be flipped on both
215 /// big and little endian machines.
216 #[inline]
217 #[must_use]
218 pub const fn from_u128_le(value: u128) -> Self {
219 Self {
220 uuid: Uuid::from_u128_le(value),
221 _phantom: PhantomData,
222 }
223 }
224
225 /// Creates a UUID from two 64bit values.
226 #[inline]
227 #[must_use]
228 pub const fn from_u64_pair(d1: u64, d2: u64) -> Self {
229 Self {
230 uuid: Uuid::from_u64_pair(d1, d2),
231 _phantom: PhantomData,
232 }
233 }
234
235 /// Creates a UUID using the supplied bytes.
236 #[inline]
237 #[must_use]
238 pub const fn from_bytes(bytes: uuid::Bytes) -> Self {
239 Self {
240 uuid: Uuid::from_bytes(bytes),
241 _phantom: PhantomData,
242 }
243 }
244
245 /// Creates a UUID using the supplied bytes in little-endian order.
246 ///
247 /// The individual fields encoded in the buffer will be flipped.
248 #[inline]
249 #[must_use]
250 pub const fn from_bytes_le(bytes: uuid::Bytes) -> Self {
251 Self {
252 uuid: Uuid::from_bytes_le(bytes),
253 _phantom: PhantomData,
254 }
255 }
256
257 /// Creates a new, random UUID v4 of this type.
258 #[inline]
259 #[cfg(feature = "v4")]
260 #[must_use]
261 pub fn new_v4() -> Self {
262 Self::from_untyped_uuid(Uuid::new_v4())
263 }
264
265 /// Returns the version number of the UUID.
266 ///
267 /// This represents the algorithm used to generate the value.
268 /// This method is the future-proof alternative to [`Self::get_version`].
269 ///
270 /// # References
271 ///
272 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
273 #[inline]
274 pub const fn get_version_num(&self) -> usize {
275 self.uuid.get_version_num()
276 }
277
278 /// Returns the version of the UUID.
279 ///
280 /// This represents the algorithm used to generate the value.
281 /// If the version field doesn't contain a recognized version then `None`
282 /// is returned. If you're trying to read the version for a future extension
283 /// you can also use [`Uuid::get_version_num`] to unconditionally return a
284 /// number. Future extensions may start to return `Some` once they're
285 /// standardized and supported.
286 ///
287 /// # References
288 ///
289 /// * [Version Field in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-4.2)
290 #[inline]
291 pub fn get_version(&self) -> Option<Version> {
292 self.uuid.get_version()
293 }
294}
295
296// ---
297// Trait impls
298// ---
299
300impl<T: TypedUuidKind> PartialEq for TypedUuid<T> {
301 #[inline]
302 fn eq(&self, other: &Self) -> bool {
303 self.uuid.eq(&other.uuid)
304 }
305}
306
307impl<T: TypedUuidKind> Eq for TypedUuid<T> {}
308
309impl<T: TypedUuidKind> PartialOrd for TypedUuid<T> {
310 #[inline]
311 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
312 Some(self.uuid.cmp(&other.uuid))
313 }
314}
315
316impl<T: TypedUuidKind> Ord for TypedUuid<T> {
317 #[inline]
318 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
319 self.uuid.cmp(&other.uuid)
320 }
321}
322
323impl<T: TypedUuidKind> Hash for TypedUuid<T> {
324 #[inline]
325 fn hash<H: Hasher>(&self, state: &mut H) {
326 self.uuid.hash(state);
327 }
328}
329
330impl<T: TypedUuidKind> fmt::Debug for TypedUuid<T> {
331 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332 self.uuid.fmt(f)?;
333 write!(f, " ({})", T::tag())
334 }
335}
336
337impl<T: TypedUuidKind> fmt::Display for TypedUuid<T> {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 self.uuid.fmt(f)
340 }
341}
342
343impl<T: TypedUuidKind> Clone for TypedUuid<T> {
344 #[inline]
345 fn clone(&self) -> Self {
346 *self
347 }
348}
349
350impl<T: TypedUuidKind> Copy for TypedUuid<T> {}
351
352impl<T: TypedUuidKind> FromStr for TypedUuid<T> {
353 type Err = ParseError;
354
355 fn from_str(s: &str) -> Result<Self, Self::Err> {
356 let uuid = Uuid::from_str(s).map_err(|error| ParseError {
357 error,
358 tag: T::tag(),
359 })?;
360 Ok(Self::from_untyped_uuid(uuid))
361 }
362}
363
364impl<T: TypedUuidKind> Default for TypedUuid<T> {
365 #[inline]
366 fn default() -> Self {
367 Self::from_untyped_uuid(Uuid::default())
368 }
369}
370
371#[cfg(feature = "schemars08")]
372mod schemars08_imp {
373 use super::*;
374 use schemars::JsonSchema;
375
376 /// Implements `JsonSchema` for `TypedUuid<T>`, if `T` implements `JsonSchema`.
377 ///
378 /// * `schema_name` is set to `"TypedUuidFor"`, concatenated by the schema name of `T`.
379 /// * `schema_id` is set to `format!("newtype_uuid::TypedUuid<{}>", T::schema_id())`.
380 /// * `json_schema` is the same as the one for `Uuid`.
381 impl<T> JsonSchema for TypedUuid<T>
382 where
383 T: TypedUuidKind + JsonSchema,
384 {
385 #[inline]
386 fn schema_name() -> String {
387 format!("TypedUuidFor{}", T::schema_name())
388 }
389
390 #[inline]
391 fn schema_id() -> std::borrow::Cow<'static, str> {
392 std::borrow::Cow::Owned(format!("newtype_uuid::TypedUuid<{}>", T::schema_id()))
393 }
394
395 #[inline]
396 fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
397 Uuid::json_schema(gen)
398 }
399 }
400}
401
402#[cfg(feature = "proptest1")]
403mod proptest1_imp {
404 use super::*;
405 use proptest::{
406 arbitrary::{any, Arbitrary},
407 strategy::{BoxedStrategy, Strategy},
408 };
409
410 /// Parameters for use with `proptest` instances.
411 ///
412 /// This is currently not exported as a type because it has no options. But
413 /// it's left in as an extension point for the future.
414 #[derive(Clone, Debug, Default)]
415 pub struct TypedUuidParams(());
416
417 /// Generates random `TypedUuid<T>` instances.
418 ///
419 /// Currently, this always returns a version 4 UUID. Support for other kinds
420 /// of UUIDs might be added via [`Self::Parameters`] in the future.
421 impl<T> Arbitrary for TypedUuid<T>
422 where
423 T: TypedUuidKind,
424 {
425 type Parameters = TypedUuidParams;
426 type Strategy = BoxedStrategy<Self>;
427
428 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
429 let bytes = any::<[u8; 16]>();
430 bytes
431 .prop_map(|b| {
432 let uuid = uuid::Builder::from_random_bytes(b).into_uuid();
433 TypedUuid::<T>::from_untyped_uuid(uuid)
434 })
435 .boxed()
436 }
437 }
438}
439
440/// Represents marker types that can be used as a type parameter for [`TypedUuid`].
441///
442/// Generally, an implementation of this will be a zero-sized type that can never be constructed. An
443/// empty struct or enum works well for this.
444///
445/// # Implementations
446///
447/// If the `schemars08` feature is enabled, and [`JsonSchema`] is implemented for a kind `T`, then
448/// [`TypedUuid`]`<T>` will also implement [`JsonSchema`].
449///
450/// # Notes
451///
452/// If you have a large number of UUID kinds, it can be repetitive to implement this trait for each
453/// kind. Here's a template for a macro that can help:
454///
455/// ```
456/// use newtype_uuid::{TypedUuidKind, TypedUuidTag};
457///
458/// macro_rules! impl_typed_uuid_kind {
459/// ($($kind:ident => $tag:literal),* $(,)?) => {
460/// $(
461/// pub enum $kind {}
462///
463/// impl TypedUuidKind for $kind {
464/// #[inline]
465/// fn tag() -> TypedUuidTag {
466/// const TAG: TypedUuidTag = TypedUuidTag::new($tag);
467/// TAG
468/// }
469/// }
470/// )*
471/// };
472/// }
473///
474/// // Invoke this macro with:
475/// impl_typed_uuid_kind! {
476/// Kind1 => "kind1",
477/// Kind2 => "kind2",
478/// }
479/// ```
480///
481/// [`JsonSchema`]: schemars::JsonSchema
482pub trait TypedUuidKind: Send + Sync + 'static {
483 /// Returns the corresponding tag for this kind.
484 ///
485 /// The tag forms a runtime representation of this type.
486 ///
487 /// The tag is required to be a static string.
488 fn tag() -> TypedUuidTag;
489}
490
491/// Describes what kind of [`TypedUuid`] something is.
492///
493/// This is the runtime equivalent of [`TypedUuidKind`].
494#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
495pub struct TypedUuidTag(&'static str);
496
497impl TypedUuidTag {
498 /// Creates a new `TypedUuidTag` from a static string.
499 ///
500 /// The string must be non-empty, and consist of:
501 /// - ASCII letters
502 /// - digits (only after the first character)
503 /// - underscores
504 /// - hyphens (only after the first character)
505 ///
506 /// # Panics
507 ///
508 /// Panics if the above conditions aren't met. Use [`Self::try_new`] to handle errors instead.
509 #[must_use]
510 pub const fn new(tag: &'static str) -> Self {
511 match Self::try_new_impl(tag) {
512 Ok(tag) => tag,
513 Err(message) => panic!("{}", message),
514 }
515 }
516
517 /// Attempts to create a new `TypedUuidTag` from a static string.
518 ///
519 /// The string must be non-empty, and consist of:
520 /// - ASCII letters
521 /// - digits (only after the first character)
522 /// - underscores
523 /// - hyphens (only after the first character)
524 ///
525 /// # Errors
526 ///
527 /// Returns a [`TagError`] if the above conditions aren't met.
528 pub const fn try_new(tag: &'static str) -> Result<Self, TagError> {
529 match Self::try_new_impl(tag) {
530 Ok(tag) => Ok(tag),
531 Err(message) => Err(TagError {
532 input: tag,
533 message,
534 }),
535 }
536 }
537
538 const fn try_new_impl(tag: &'static str) -> Result<Self, &'static str> {
539 if tag.is_empty() {
540 return Err("tag must not be empty");
541 }
542
543 let bytes = tag.as_bytes();
544 if !(bytes[0].is_ascii_alphabetic() || bytes[0] == b'_') {
545 return Err("first character of tag must be an ASCII letter or underscore");
546 }
547
548 let mut bytes = match bytes {
549 [_, rest @ ..] => rest,
550 [] => panic!("already checked that it's non-empty"),
551 };
552 while let [rest @ .., last] = &bytes {
553 if !(last.is_ascii_alphanumeric() || *last == b'_' || *last == b'-') {
554 break;
555 }
556 bytes = rest;
557 }
558
559 if !bytes.is_empty() {
560 return Err("tag must only contain ASCII letters, digits, underscores, or hyphens");
561 }
562
563 Ok(Self(tag))
564 }
565
566 /// Returns the tag as a string.
567 pub const fn as_str(&self) -> &'static str {
568 self.0
569 }
570}
571
572impl fmt::Display for TypedUuidTag {
573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
574 f.write_str(self.0)
575 }
576}
577
578impl AsRef<str> for TypedUuidTag {
579 fn as_ref(&self) -> &str {
580 self.0
581 }
582}
583
584/// An error that occurred while creating a [`TypedUuidTag`].
585#[derive(Clone, Debug)]
586#[non_exhaustive]
587pub struct TagError {
588 /// The input string.
589 pub input: &'static str,
590
591 /// The error message.
592 pub message: &'static str,
593}
594
595impl fmt::Display for TagError {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 write!(
598 f,
599 "error creating tag from '{}': {}",
600 self.input, self.message
601 )
602 }
603}
604
605#[cfg(feature = "std")]
606impl std::error::Error for TagError {}
607
608/// An error that occurred while parsing a [`TypedUuid`].
609#[derive(Clone, Debug)]
610#[non_exhaustive]
611pub struct ParseError {
612 /// The underlying error.
613 pub error: uuid::Error,
614
615 /// The tag of the UUID that failed to parse.
616 pub tag: TypedUuidTag,
617}
618
619impl fmt::Display for ParseError {
620 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
621 write!(f, "error parsing UUID ({})", self.tag)
622 }
623}
624
625#[cfg(feature = "std")]
626impl std::error::Error for ParseError {
627 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
628 Some(&self.error)
629 }
630}
631
632/// A trait abstracting over typed and untyped UUIDs.
633///
634/// This can be used to write code that's generic over [`TypedUuid`], [`Uuid`], and other types that
635/// may wrap [`TypedUuid`] (due to e.g. orphan rules).
636///
637/// This trait is similar to `From`, but a bit harder to get wrong -- in general, the conversion
638/// from and to untyped UUIDs should be careful and explicit.
639pub trait GenericUuid {
640 /// Creates a new instance of `Self` from an untyped [`Uuid`].
641 #[must_use]
642 fn from_untyped_uuid(uuid: Uuid) -> Self
643 where
644 Self: Sized;
645
646 /// Converts `self` into an untyped [`Uuid`].
647 #[must_use]
648 fn into_untyped_uuid(self) -> Uuid
649 where
650 Self: Sized;
651
652 /// Returns the inner [`Uuid`].
653 ///
654 /// Generally, [`into_untyped_uuid`](Self::into_untyped_uuid) should be preferred. However,
655 /// in some cases it may be necessary to use this method to satisfy lifetime constraints.
656 fn as_untyped_uuid(&self) -> &Uuid;
657}
658
659impl GenericUuid for Uuid {
660 #[inline]
661 fn from_untyped_uuid(uuid: Uuid) -> Self {
662 uuid
663 }
664
665 #[inline]
666 fn into_untyped_uuid(self) -> Uuid {
667 self
668 }
669
670 #[inline]
671 fn as_untyped_uuid(&self) -> &Uuid {
672 self
673 }
674}
675
676impl<T: TypedUuidKind> GenericUuid for TypedUuid<T> {
677 #[inline]
678 fn from_untyped_uuid(uuid: Uuid) -> Self {
679 Self {
680 uuid,
681 _phantom: PhantomData,
682 }
683 }
684
685 #[inline]
686 fn into_untyped_uuid(self) -> Uuid {
687 self.uuid
688 }
689
690 #[inline]
691 fn as_untyped_uuid(&self) -> &Uuid {
692 &self.uuid
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699
700 #[test]
701 fn test_validate_tags() {
702 for &valid_tag in &[
703 "a", "a-", "a_", "a-b", "a_b", "a1", "a1-", "a1_", "a1-b", "a1_b", "_a",
704 ] {
705 TypedUuidTag::try_new(valid_tag).expect("tag is valid");
706 // Should not panic
707 _ = TypedUuidTag::new(valid_tag);
708 }
709
710 for invalid_tag in &["", "1", "-", "a1b!", "a1-b!", "a1_b:", "\u{1f4a9}"] {
711 TypedUuidTag::try_new(invalid_tag).unwrap_err();
712 }
713 }
714
715 // This test just ensures that `GenericUuid` is object-safe.
716 #[test]
717 #[cfg(all(feature = "v4", feature = "std"))]
718 fn test_generic_uuid_object_safe() {
719 let uuid = Uuid::new_v4();
720 let box_uuid = Box::new(uuid) as Box<dyn GenericUuid>;
721 assert_eq!(box_uuid.as_untyped_uuid(), &uuid);
722 }
723}