Expand description
Domain-typed identifiers: Account, Currency, Tag, Link.
These newtype wrappers around InternedStr give the type system
enough vocabulary to distinguish the different kinds of identifier
the beancount AST carries. Pre-newtype, every identifier was just
an InternedStr — passing an account where a currency was
expected (or vice versa) compiled fine, and the bug surfaced
only at runtime via wrong-but-validly-shaped string matching.
Now the same mistake is a type error.
§Design
Each newtype is a transparent wrapper:
Deref<Target = str>so calls likeaccount.starts_with("Assets:")work without.as_str()everywhere.AsRef<str>andBorrow<str>soHashMaplookups by&strkeep working (some_map.get("Assets:Bank")where the map is keyed byAccount).PartialEqagainststr/&str/String/InternedStr/ the newtype’s own type, soaccount == "Assets:Bank"keeps reading naturally without coercion.From<&str>,From<String>,From<InternedStr>for construction at call sites that have a string and need the typed form.Hashdelegates to the innerInternedStr’s hash, soHashMap<Account, V>andHashMap<InternedStr, V>produce the same bucketing for the same underlying string.
What you DON’T get for free is cross-newtype assignment:
fn want_currency(_: Currency) {}
let acct = Account::from("Assets:Bank");
want_currency(acct); // ← type errorConversions between newtypes are deliberate (Currency::from(account.into_interned()))
so the compiler can flag accidental crossings.
§When to use which
All four newtypes — Currency, Account, Tag, and
Link — are fully plumbed through the AST, including
MetaValue variants:
Currency:Commodity.currency,Open.currenciesentries,Amount.currency,CostSpec.currency,Price.currency,IncompleteAmount::CurrencyOnly,MetaValue::Currency.Account:Open.account,Close.account,Balance.account,Pad.account/source_account,Note.account,Document.account,Posting.account,MetaValue::Account.Tag:Transaction.tagsentries,pushtag/poptagstack,Document.tags,MetaValue::Tag.Link:Transaction.linksentries,Document.links,MetaValue::Link.
The plugin wire-format type rustledger_plugin_types::MetaValueData
deliberately keeps String payloads — plugin-types is a minimal
WASM-compatible crate that does not depend on rustledger-core,
and plugins run without access to the workspace interner anyway.
The convert boundary
(rustledger_plugin::convert::from_wrapper) wraps the incoming
strings in fresh Arc<str>s; the cross-file canonicalization to
one Arc<str> per identifier string happens later in
rustledger_loader::dedup::reintern_directives, which walks both
AST identifier fields and MetaValue::* payloads inside metadata.
Structs§
- Account
- Domain-typed identifier for a beancount account name (e.g.
Assets:Cash:USD). See the module docs for rationale. - Account
Resolver - The resolver for an archived
Account - Archived
Account - An archived
Account - Archived
Currency - An archived
Currency - Archived
Link - An archived
Link - Archived
Tag - An archived
Tag - Currency
- Domain-typed identifier for a currency code (e.g.
USD,EUR,AAPL). See the module docs for rationale. - Currency
Resolver - The resolver for an archived
Currency - Link
- Domain-typed identifier for a beancount link (e.g.
^invoice-2024-01). See the module docs for rationale. - Link
Resolver - The resolver for an archived
Link - Tag
- Domain-typed identifier for a beancount tag (e.g.
#travel). See the module docs for rationale. - TagResolver
- The resolver for an archived
Tag
Constants§
- ACCOUNT_
TYPES - The five Beancount root account types, in declaration order.
Functions§
- account_
type - The lowercased root account type for
account— the segment before the first:— or"unknown"if it is not one ofACCOUNT_TYPES. - is_
subaccount_ or_ equal - Returns
trueifchildis the same account asparent, or a sub-account of it.