Skip to main content

net_lattice_core/
id.rs

1use std::fmt;
2use std::hash::{Hash, Hasher};
3use std::marker::PhantomData;
4
5/// A phantom-typed identifier, generic over the domain object it names.
6///
7/// `net-lattice-model` defines per-domain aliases (`type RouteId = Id<Route>;`)
8/// instead of hand-writing lookalike structs (`RouteId`, `InterfaceId`,
9/// ...). Passing a `RouteId` where an `InterfaceId` is expected fails to
10/// compile, rather than silently looking up the wrong object.
11///
12/// `Id<T>` serializes as its bare underlying value, independent of `T` —
13/// see ARCHITECTURE.md's note on `Id<T>`'s serialization stability.
14pub struct Id<T> {
15    value: u64,
16    _marker: PhantomData<fn() -> T>,
17}
18
19impl<T> Id<T> {
20    pub const fn new(value: u64) -> Self {
21        Self {
22            value,
23            _marker: PhantomData,
24        }
25    }
26
27    pub const fn value(&self) -> u64 {
28        self.value
29    }
30}
31
32impl<T> Clone for Id<T> {
33    fn clone(&self) -> Self {
34        *self
35    }
36}
37
38impl<T> Copy for Id<T> {}
39
40impl<T> PartialEq for Id<T> {
41    fn eq(&self, other: &Self) -> bool {
42        self.value == other.value
43    }
44}
45
46impl<T> Eq for Id<T> {}
47
48impl<T> Hash for Id<T> {
49    fn hash<H: Hasher>(&self, state: &mut H) {
50        self.value.hash(state);
51    }
52}
53
54impl<T> fmt::Debug for Id<T> {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        f.debug_tuple("Id").field(&self.value).finish()
57    }
58}
59
60impl<T> fmt::Display for Id<T> {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62        write!(f, "{}", self.value)
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    struct Route;
71    struct Interface;
72
73    #[test]
74    fn same_value_is_equal_within_the_same_domain() {
75        assert_eq!(Id::<Route>::new(1), Id::<Route>::new(1));
76        assert_ne!(Id::<Route>::new(1), Id::<Route>::new(2));
77    }
78
79    #[test]
80    fn different_domains_are_distinct_types() {
81        // This is a compile-time property: `Id<Route>` and `Id<Interface>`
82        // are different types, so a function expecting one cannot accept
83        // the other. There is nothing to assert at runtime; this test
84        // exists to keep both markers constructible and exercised.
85        let route_id = Id::<Route>::new(1);
86        let interface_id = Id::<Interface>::new(1);
87        assert_eq!(route_id.value(), interface_id.value());
88    }
89
90    #[test]
91    fn value_round_trips() {
92        assert_eq!(Id::<Route>::new(42).value(), 42);
93    }
94
95    #[test]
96    fn id_clone_hash_and_format_use_the_underlying_value() {
97        use std::collections::hash_map::DefaultHasher;
98
99        let id = Id::<Route>::new(42);
100        assert_eq!(id, id.clone());
101        assert_eq!(id.to_string(), "42");
102        assert_eq!(format!("{id:?}"), "Id(42)");
103
104        let mut first = DefaultHasher::new();
105        id.hash(&mut first);
106        let mut second = DefaultHasher::new();
107        Id::<Route>::new(42).hash(&mut second);
108        assert_eq!(first.finish(), second.finish());
109    }
110}