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}