Skip to main content

molframe_core/symbol/
altloc.rs

1//! Alternate-location labels.
2
3use super::interner::SymbolId;
4use std::fmt;
5
6/// An alternate-location label.
7///
8/// The blank label means "this atom belongs to every conformation" — a residue
9/// routinely mixes blank-labelled backbone atoms with labelled side-chain ones,
10/// which is exactly why picking the highest-occupancy atom one at a time can
11/// assemble a conformation that was never modelled.
12///
13/// A label of any length is representable, because refusing to truncate applies
14/// to labels as much as to chain identifiers.
15///
16/// # Examples
17///
18/// ```
19/// use molframe_core::{AltId, Interner};
20///
21/// let mut interner = Interner::new();
22/// let a = AltId::labelled(interner.intern("A")?);
23/// assert!(a.is_some_and(|label| !label.is_blank()));
24/// assert!(AltId::BLANK.is_blank());
25/// # Ok::<(), molframe_core::DictionaryFull>(())
26/// ```
27#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
28#[repr(transparent)]
29pub struct AltId(u32);
30
31impl AltId {
32    /// The label carried by an atom present in every conformation.
33    pub const BLANK: Self = Self(0);
34
35    /// Creates a label from an interned string.
36    ///
37    /// Identifiers emitted by [`crate::Interner`] are guaranteed to fit this
38    /// offset encoding without overflowing.
39    ///
40    /// Runs in `O(1)` time and allocates no memory.
41    #[must_use]
42    #[inline]
43    pub const fn labelled(symbol: SymbolId) -> Option<Self> {
44        match symbol.get().checked_add(1) {
45            Some(raw) => Some(Self(raw)),
46            None => None,
47        }
48    }
49
50    /// Returns true for the blank label.
51    ///
52    /// Runs in `O(1)` time and allocates no memory.
53    #[must_use]
54    #[inline]
55    pub const fn is_blank(self) -> bool {
56        self.0 == 0
57    }
58
59    /// The interned label, or `None` when blank.
60    ///
61    /// Runs in `O(1)` time and allocates no memory.
62    #[must_use]
63    #[inline]
64    pub const fn symbol(self) -> Option<SymbolId> {
65        match self.0 {
66            0 => None,
67            raw => Some(SymbolId::from_raw(raw - 1)),
68        }
69    }
70
71    /// The raw value, for storage in a column.
72    ///
73    /// Runs in `O(1)` time and allocates no memory.
74    #[must_use]
75    #[inline]
76    pub const fn get(self) -> u32 {
77        self.0
78    }
79
80    /// Rebuilds a label from a stored value.
81    ///
82    /// Runs in `O(1)` time and allocates no memory.
83    #[must_use]
84    #[inline]
85    pub const fn from_raw(raw: u32) -> Self {
86        Self(raw)
87    }
88}
89
90impl fmt::Debug for AltId {
91    /// Formats the blank label or its underlying symbol.
92    ///
93    /// Runs in `O(1)` structural work plus the cost of formatting the symbol,
94    /// and allocates no intermediate heap storage.
95    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96        match self.symbol() {
97            Some(symbol) => write!(f, "AltId({symbol:?})"),
98            None => f.write_str("AltId(blank)"),
99        }
100    }
101}
102
103#[cfg(test)]
104#[path = "altloc_tests.rs"]
105mod tests;