Skip to main content

use_element/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4//! Basic chemical element primitives and static lookup helpers.
5
6mod data;
7
8use data::ELEMENTS;
9
10/// Small copyable element metadata suitable for direct lookup helpers.
11#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Element {
13    pub atomic_number: u8,
14    pub symbol: &'static str,
15    pub name: &'static str,
16    pub atomic_mass: f64,
17    pub period: u8,
18    pub group: Option<u8>,
19}
20
21impl Element {
22    /// Creates an element metadata value.
23    #[must_use]
24    pub const fn new(
25        atomic_number: u8,
26        symbol: &'static str,
27        name: &'static str,
28        atomic_mass: f64,
29        period: u8,
30        group: Option<u8>,
31    ) -> Self {
32        Self {
33            atomic_number,
34            symbol,
35            name,
36            atomic_mass,
37            period,
38            group,
39        }
40    }
41}
42
43/// Returns the full static element table in atomic-number order.
44#[must_use]
45pub fn all_elements() -> &'static [Element] {
46    &ELEMENTS
47}
48
49/// Looks up an element by symbol using ASCII case-insensitive matching.
50///
51/// Leading and trailing whitespace are ignored.
52///
53/// # Examples
54///
55/// ```rust
56/// use use_element::element_by_symbol;
57///
58/// let sodium = element_by_symbol(" na ").unwrap();
59///
60/// assert_eq!(sodium.atomic_number, 11);
61/// assert_eq!(sodium.symbol, "Na");
62/// ```
63#[must_use]
64pub fn element_by_symbol(symbol: &str) -> Option<Element> {
65    let normalized = symbol.trim();
66
67    if normalized.is_empty() {
68        return None;
69    }
70
71    ELEMENTS
72        .iter()
73        .copied()
74        .find(|element| element.symbol.eq_ignore_ascii_case(normalized))
75}
76
77/// Looks up an element by atomic number.
78///
79/// # Examples
80///
81/// ```rust
82/// use use_element::element_by_atomic_number;
83///
84/// let oxygen = element_by_atomic_number(8).unwrap();
85///
86/// assert_eq!(oxygen.symbol, "O");
87/// assert_eq!(oxygen.name, "Oxygen");
88/// ```
89#[must_use]
90pub fn element_by_atomic_number(atomic_number: u8) -> Option<Element> {
91    atomic_number
92        .checked_sub(1)
93        .and_then(|index| ELEMENTS.get(usize::from(index)))
94        .copied()
95}
96
97/// Returns the element name for a symbol lookup.
98///
99/// # Examples
100///
101/// ```rust
102/// use use_element::element_name;
103///
104/// assert_eq!(element_name("Au"), Some("Gold"));
105/// assert_eq!(element_name("??"), None);
106/// ```
107#[must_use]
108pub fn element_name(symbol: &str) -> Option<&'static str> {
109    element_by_symbol(symbol).map(|element| element.name)
110}
111
112/// Returns the element symbol for an atomic number.
113///
114/// # Examples
115///
116/// ```rust
117/// use use_element::element_symbol;
118///
119/// assert_eq!(element_symbol(79), Some("Au"));
120/// assert_eq!(element_symbol(0), None);
121/// ```
122#[must_use]
123pub fn element_symbol(atomic_number: u8) -> Option<&'static str> {
124    element_by_atomic_number(atomic_number).map(|element| element.symbol)
125}
126
127#[cfg(test)]
128mod tests {
129    use super::{
130        all_elements, element_by_atomic_number, element_by_symbol, element_name, element_symbol,
131    };
132
133    #[test]
134    fn exposes_all_known_elements() {
135        assert_eq!(all_elements().len(), 118);
136        assert_eq!(
137            all_elements().first().map(|element| element.symbol),
138            Some("H")
139        );
140        assert_eq!(
141            all_elements().last().map(|element| element.symbol),
142            Some("Og")
143        );
144    }
145
146    #[test]
147    fn looks_up_expected_symbols_case_insensitively() {
148        assert_eq!(
149            element_by_symbol("H").map(|element| element.atomic_number),
150            Some(1)
151        );
152        assert_eq!(
153            element_by_symbol("c").map(|element| element.name),
154            Some("Carbon")
155        );
156        assert_eq!(
157            element_by_symbol("O").map(|element| element.atomic_number),
158            Some(8)
159        );
160        assert_eq!(
161            element_by_symbol(" na ").map(|element| element.atomic_number),
162            Some(11)
163        );
164        assert_eq!(
165            element_by_symbol("Fe").map(|element| element.name),
166            Some("Iron")
167        );
168        assert_eq!(
169            element_by_symbol("Au").map(|element| element.atomic_number),
170            Some(79)
171        );
172        assert_eq!(
173            element_by_symbol("U").map(|element| element.name),
174            Some("Uranium")
175        );
176        assert_eq!(
177            element_by_symbol("Og").map(|element| element.atomic_number),
178            Some(118)
179        );
180        assert_eq!(element_by_symbol("Xx"), None);
181        assert_eq!(element_by_symbol("   "), None);
182    }
183
184    #[test]
185    fn looks_up_expected_atomic_numbers() {
186        assert_eq!(
187            element_by_atomic_number(1).map(|element| element.symbol),
188            Some("H")
189        );
190        assert_eq!(
191            element_by_atomic_number(6).map(|element| element.name),
192            Some("Carbon")
193        );
194        assert_eq!(
195            element_by_atomic_number(8).map(|element| element.symbol),
196            Some("O")
197        );
198        assert_eq!(
199            element_by_atomic_number(11).map(|element| element.symbol),
200            Some("Na")
201        );
202        assert_eq!(
203            element_by_atomic_number(26).map(|element| element.name),
204            Some("Iron")
205        );
206        assert_eq!(
207            element_by_atomic_number(79).map(|element| element.name),
208            Some("Gold")
209        );
210        assert_eq!(
211            element_by_atomic_number(92).map(|element| element.name),
212            Some("Uranium")
213        );
214        assert_eq!(
215            element_by_atomic_number(118).map(|element| element.name),
216            Some("Oganesson")
217        );
218        assert_eq!(element_by_atomic_number(0), None);
219        assert_eq!(element_by_atomic_number(119), None);
220    }
221
222    #[test]
223    fn exposes_name_and_symbol_helpers() {
224        assert_eq!(element_name("H"), Some("Hydrogen"));
225        assert_eq!(element_name("na"), Some("Sodium"));
226        assert_eq!(element_name("invalid"), None);
227        assert_eq!(element_symbol(8), Some("O"));
228        assert_eq!(element_symbol(79), Some("Au"));
229        assert_eq!(element_symbol(119), None);
230    }
231
232    #[test]
233    fn stores_expected_period_and_group_metadata() {
234        let hydrogen = element_by_symbol("H").expect("hydrogen should exist");
235        let carbon = element_by_symbol("C").expect("carbon should exist");
236        let iron = element_by_symbol("Fe").expect("iron should exist");
237        let uranium = element_by_symbol("U").expect("uranium should exist");
238        let oganesson = element_by_symbol("Og").expect("oganesson should exist");
239
240        assert_eq!((hydrogen.period, hydrogen.group), (1, Some(1)));
241        assert_eq!((carbon.period, carbon.group), (2, Some(14)));
242        assert_eq!((iron.period, iron.group), (4, Some(8)));
243        assert_eq!((uranium.period, uranium.group), (7, None));
244        assert_eq!((oganesson.period, oganesson.group), (7, Some(18)));
245    }
246}