Rust port of the Haskell base-deriving-via package. It offers a lightweight
way to derive Semigroup/Monoid implementations from a “generic” tuple
representation, mirroring the deriving via pattern used across the Cardano
code base.
InstantiatedAt<T>wrapper — grantsSemigroup/Monoidinstances to your type once a compatible generic representation exists.Generic,GenericSemigroup,GenericMonoidtraits — encode the mapping between the concrete type and its tuple-based representation.impl_generic_for_struct!macro — derivesGenericautomatically for common record and tuple structs without relying onunsafe.- Blanket tuple implementations — tuples up to arity eight already provide
Semigroup/Monoid, covering most ledger records out of the box. - Standard library support — integers, durations, strings, vectors, arrays, and options ship with ready-made instances.
- Pure Rust — no macros beyond declarative
macro_rules!, no runtime reflection, and no external dependencies.
use base_deriving_via::{
impl_generic_for_struct, InstantiatedAt, Monoid, Semigroup,
};
#[derive(Debug, Clone, PartialEq, Eq)]
struct Counter {
label: String,
ticks: i64,
}
impl_generic_for_struct!(struct Counter {
label: String,
ticks: i64,
});
let combined = InstantiatedAt::new(Counter {
label: "alpha".into(),
ticks: 2,
})
.combine(InstantiatedAt::new(Counter {
label: "beta".into(),
ticks: 5,
}))
.into_inner();
assert_eq!(combined.label, "alphabeta");
assert_eq!(combined.ticks, 7);Generic derivation works for tuple structs as well. You can also hand-roll a representation when the default tuple layout is insufficient:
use base_deriving_via::{Generic, GenericMonoid, InstantiatedAt, Monoid};
#[derive(Debug, Clone, PartialEq, Eq)]
struct RollingAverage {
sum: i64,
count: u32,
}
// Implement Generic manually to encode additional invariants.
impl Generic for RollingAverage {
type Repr = (i64, u32);
type ReprRef<'a> = (&'a i64, &'a u32) where Self: 'a;
fn into_repr(self) -> Self::Repr {
(self.sum, self.count)
}
fn from_repr(repr: Self::Repr) -> Self {
RollingAverage {
sum: repr.0,
count: repr.1,
}
}
fn as_repr(&self) -> Self::ReprRef<'_> {
(&self.sum, &self.count)
}
}
impl GenericMonoid for RollingAverage {}
let average = InstantiatedAt::new(RollingAverage { sum: 6, count: 2 })
.combine(InstantiatedAt::new(RollingAverage { sum: 9, count: 3 }))
.into_inner();
assert_eq!(average.sum, 15);
assert_eq!(average.count, 5);| Haskell module/symbol | Rust counterpart |
|---|---|
Data.DerivingVia.InstantiatedAt |
base_deriving_via::InstantiatedAt |
Data.DerivingVia.Generic |
base_deriving_via::Generic |
Data.DerivingVia.GenericSemigroup |
base_deriving_via::GenericSemigroup |
Data.DerivingVia.GenericMonoid |
base_deriving_via::GenericMonoid |
implGenericForStruct TH helper |
base_deriving_via::impl_generic_for_struct! |
Tuple Semigroup/Monoid instances |
base_deriving_via::semigroup module |
- Downstream crates (
deepseq,nothunks,orphans-deriving-via) rely on theInstantiatedAtwrapper to bridge evaluation traits. Keep version alignment across the workspace to avoid API drift. - The current macro supports record structs and tuple structs. If you need enum
coverage, add a new macro pattern or implement
Genericmanually. - The crate has no optional features; it is safe to depend on in
no_stdcontexts that support heap allocations forString/Vec.
Run the crate tests to validate the derivations:
cargo test -p base-deriving-viaThe suite mirrors the Haskell QuickCheck coverage for InstantiatedAt, tuple
instances, and generic macro behaviour.
Dual-licensed under Apache-2.0 or MIT. See LICENSE and
NOTICE in the workspace root.