Expand description
ISIN (International Securities Identification Number) — the ISO 6166 identifier for a fungible financial security.
This module provides the validated Rust representation (Isin) and the parsing, validation,
and error types that surround it. It accepts the canonical 12-character form (optionally
surrounded by whitespace, in any ASCII case), normalizes it, and guarantees that any constructed
Isin satisfies the structural rules and the Luhn check digit described below. There is no
partially-validated state: if you hold an Isin, it is valid.
§What this type represents
An ISIN has 12 characters, split into three segments:
| Positions | Length | Segment | Meaning |
|---|---|---|---|
| 1–2 | 2 | Country code | ISO 3166-1 alpha-2 prefix of the issuing national numbering agency |
| 3–11 | 9 | NSIN | National Securities Identifying Number, alphanumeric |
| 12 | 1 | Check digit | Luhn (modulus 10) digit computed over the first 11 characters |
┌──────────────────────────────────────────────────────┐
│ CC │ NSIN (9 chars) │ Check (1) │
│ A A │ N N N N N N N N N │ D │
└──────────────────────────────────────────────────────┘Isin stores those 12 characters as normalized uppercase ASCII and exposes borrowed accessors
for the country code (Isin::country_code), the NSIN (Isin::nsin), the check digit
(Isin::check_digit), and the whole value (Isin::as_str).
§Validation rules
Every fallible constructor runs the same rules, in order, and each maps to one IsinError
variant:
- Length — after surrounding whitespace is trimmed, the input must contain exactly 12
characters (
IsinError::InvalidLength).Isin::parserejects empty input up front (IsinError::Empty). - Character class — positions 1–2 accept an uppercase letter, positions 3–11 accept a digit
or an uppercase letter, and position 12 accepts only a digit (
IsinError::InvalidCharacter). - Check digit — position 12 must match the ISO 6166 Luhn digit computed from the first 11
characters (
IsinError::InvalidCheckDigit).
The country code is validated structurally (two uppercase letters); this crate deliberately does not check it against the live ISO 3166-1 country registry, which changes over time and is out of scope for a checksum-oriented value type.
§Design notes
- No invalid state is representable.
Isin’s only field is private; the only ways to obtain one —Isin::parse,Isin::new,Isin::from_bytes,FromStr, andTryFrom<&str>— all run full validation. There is no unchecked constructor. - Zero allocation,
Copy,no_std-friendly.Isinis a 12-byte value type wrapping[u8; 12]. Parsing, validating, and every accessor operate on the stack. - Ordering and hashing are byte-wise.
IsinderivesOrdandHashdirectly over its ASCII bytes, which matchesstrordering onIsin::as_str. This is lexicographic string order, not any notion of issuance order. - Safe to use as a map/set key.
IsinimplementsEqandHashconsistently withPartialEq, so it works as aHashMap/HashSetorBTreeMap/BTreeSetkey out of the box.
§Feature flags
This module’s optional integrations are off by default and purely additive — enabling one never
changes the behavior of Isin::parse or the validation rules above:
serde— (de)serializesIsinas its 12-character string (e.g."US0378331005"). Deserialization re-runs full validation, so an untrusted payload can never produce an invalidIsin.schemars— implementsJsonSchemaforIsin, describing it as a pattern-constrained string (^[A-Z]{2}[A-Z0-9]{9}[0-9]$). Impliesserde.arbitrary— implementsArbitraryforIsin, generating structurally valid, checksum-correct values for fuzz targets.proptest— exposes reusablepropteststrategies (ftracker_identifiers::isin::proptest, when this feature is enabled) for generating checksum-validIsinvalues.
§Error handling
Every fallible constructor returns IsinError, which is Clone + PartialEq + Eq and
implements core::error::Error and core::fmt::Display, so it composes with ? and with
error-aggregation crates alike:
use ftracker_identifiers::{Isin, IsinError};
match Isin::parse("US0378331006") {
Ok(isin) => println!("valid: {isin}"),
Err(IsinError::InvalidCheckDigit { expected, found }) => {
println!("checksum mismatch: expected {expected}, found {found}");
}
Err(other) => println!("rejected: {other}"),
}§Examples
use ftracker_identifiers::Isin;
let apple = Isin::parse("US0378331005").unwrap();
assert_eq!(apple.country_code(), "US");
assert_eq!(apple.nsin(), "037833100");
assert_eq!(apple.check_digit(), 5);
assert_eq!(apple.as_str(), "US0378331005");Sorting and deduplicating a batch of ISINs, e.g. after importing them from a spreadsheet:
use ftracker_identifiers::Isin;
let mut isins: Vec<Isin> = ["US0231351067", "US0378331005", "US0378331005"]
.into_iter()
.map(|s| Isin::parse(s).unwrap())
.collect();
isins.sort();
isins.dedup();
assert_eq!(isins.len(), 2);Modules§
Structs§
- Isin
- A validated ISIN (International Securities Identification Number, ISO 6166).
Enums§
- Isin
Error - The set of reasons an ISIN string can fail validation.