score-set
A #![no_std] + alloc Rust library for building weighted scoring
operators — define metrics via a builder pipeline, combine them with weights
into a zero-vtable flat scorer, and evaluate against any context.
Three concepts:
Metric32<C, F>— a single named metric: measure closure + Map01 normalizationscore_set32!— macro that packs heterogeneous metrics into a flat tuple scorerScored32<C, T>— the concrete scorer with.score(&C)and.breakdown(&C)
Quick example
use *;
// Define metrics — by() accepts closures (including capturing ones)
let clean = metric32
.measure
.by
.map01
.linear;
let food = metric32
.measure
.by
.map01
.identity;
// Combine into a flat scorer (zero vtable, static dispatch)
let scorer = score_set32! ?;
let r = Restaurant ;
let total: f32 = scorer.score; // ~0.87
// Per-metric breakdown
let rows = scorer.breakdown;
for row in rows
# Ok::
Partial application (capturing closures)
let threshold: f32 = 0.6;
let quality = metric32
.measure
.by
.map01
.identity;
// threshold is baked into the closure — scorer only needs &MyCtx
let scorer = score_set32! ?;
let result = scorer.score;
Precision
| Feature flag | Available types |
|---|---|
| (default) | Metric32, Scored32, Breakdown32, metric32(), score_set32!, … |
f64 |
Metric64, Scored64, Breakdown64, metric64(), score_set64!, … |
both |
all of the above |
[]
= "0.6" # f32 only
= { = "0.6", = ["f64"] } # f64 only
= { = "0.6", = ["both"] } # both
Building a metric
metric32("name") // MetricNamingStage32
.measure() // MeasureStage32
.by(|ctx| raw) // MeasuredStage32<C, F> — impl Fn(&C) -> f32
.map01() // Map01Stage32<C, F>
.<shape>(...) // Metric32<C, F>
by() accepts any impl Fn(&C) -> f32 — function pointers, non-capturing
closures, and capturing closures all work.
Map01 shapes
| Shape | Constructor | Formula |
|---|---|---|
| Identity | .identity() |
raw.clamp(0, 1) |
| Linear | .linear(max) |
raw / max, clamped |
| Increasing sigmoid | .inc_sigmoid(low, high) |
1/(1 + e⁻ᵏ⁽ˣ⁻ˣ⁰⁾), k auto-calibrated |
| Decreasing sigmoid | .dec_sigmoid(low, high) |
1/(1 + eᵏ⁽ˣ⁻ˣ⁰⁾), k auto-calibrated |
| Asymmetric Cauchy | .cauchy(center, half_left, half_right) |
1/(1 + ((x−c)/h)²), h per-side |
| Custom | .by(fn(f32) -> f32) |
user-provided fn pointer |
All variants (except Custom) guarantee output in [0, 1] by construction.
API
score_set32! — heterogeneous scorer
score_set32! ?
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// weight => metric pairs, comma-separated, up to 64 metrics
// Returns Result<Scored32<C, T>, &'static str>
Weights must be finite and strictly positive (> 0). The macro normalizes them to sum to 1 at construction time.
Scored32<C, T> — the concrete scorer
T is a flat tuple of Metric32<C, F> types — each F can be a different
closure type. Trait dispatch is fully static (zero vtable).
Metric32<C, F> — single metric
Breakdown32
Arity limits
By default, score_set32! supports up to 8 metrics. Enable higher arities via
feature flags:
= { = "0.6", = ["level-16"] } # up to 16
= { = "0.6", = ["level-32"] } # up to 32
= { = "0.6", = ["level-64"] } # up to 64
Generated files (gen_score_set32.rs / gen_score_set64.rs) are maintained by
cargo run -p xtask -- gen --max <N>.
no_std
This crate is #![no_std] with extern crate alloc. It only needs Vec
from the allocator and libm for exp and log. Works on bare-metal targets.
License
MIT OR Apache-2.0