Qubit Value
qubit-value gives Rust applications one type-safe boundary for values that are
known only at runtime. It is useful when configuration, metadata, protocol
fields, or user input can be a boolean, number, string, date, collection, or
structured JSON value, but the application still needs explicit types,
controlled conversion, and predictable errors.
The problem it solves
Without a shared runtime value model, each key-value subsystem tends to invent
its own enum, conversion rules, unset semantics, and serialization format.
That creates three recurring problems:
- a missing value, an explicitly empty collection, and JSON
nullare easily confused; - a one-item collection can be accidentally treated as a scalar;
- values crossing a process or storage boundary lose their runtime type, or accept conversions that were never intended.
Value stores one typed scalar, MultiValues stores one homogeneous
collection, and ValueContainer preserves the explicit scalar-or-collection
shape. Unset(DataType) retains the declared type without pretending that a
concrete value exists.
Quick start: a small runtime configuration map
This is a small configuration-like map: each key stores a different Value,
then the reader chooses strict access, explicit conversion, or a typed default.
The snippet assumes it is inside a function that returns a compatible Result,
so ? can propagate value errors.
use HashMap;
use Duration;
use DataType;
use Value;
let config = from;
let host: String = config.get?;
assert_eq!;
let port: u16 = config.to?;
assert_eq!;
let debug: bool = config.get?;
assert!;
let timeout: Duration = config.get_or?;
assert_eq!;
If you need a complete, general-purpose configuration object instead of
assembling a map yourself, use Config from
rs-config. It builds on Value and
adds higher-level capabilities such as property management, typed and
multi-value reads, defaults, sections, conversion policies, interpolation, and
pluggable file/environment configuration sources.
get() is a strict type read: it does not silently convert. to() uses the
shared conversion rules from qubit-datatype; failed conversions remain
errors. The converter feature is required for to() and to_or(); get_or()
only supplies a fallback for an unset value and does not convert.
Use to_with when the boundary needs an explicit policy and limits. Every
to_with call creates a fresh ConversionSession, so independent reads do not
share cumulative consumption:
To share limits across related reads, use the _in APIs with one
ConversionSession; see Shared conversion session.
use ConversionLimits;
use ConversionPolicy;
use Value;
let policy = env_friendly;
let limits = default;
let first = new.?;
let second = new.?;
assert_eq!;
Installation
Add the core crate and its type vocabulary to Cargo.toml:
[]
= { = "0.13", = false, = ["converter"] }
= { = "0.15", = false }
The quick-start example uses Value::to, so it enables converter. The
default feature set is empty; enable only the families you use:
| Feature | Additional DataType or capability |
|---|---|
converter |
Cross-type conversion APIs such as Value::to |
chrono |
Date, Time, DateTime, and Instant |
big-integer |
BigInteger backed by num_bigint::BigInt |
big-decimal |
BigDecimal backed by bigdecimal::BigDecimal |
big-number |
Compatibility alias for both big-number features |
url |
Url backed by url::Url |
json |
Json backed by serde_json::Value and bounded versioned JSON Wire encoding/decoding |
natural-json |
Convenience alias for converter + json; their combination also enables Natural JSON |
redact |
Policy-aware redacted views through qubit-redact |
all |
converter, chrono, big-number, url, json, natural-json, and redact |
What it provides
ValueandMultiValueshave typed constructors, typed getters, generic mutation, borrowed reads, and explicit unset state.view()exposes borrowed runtime variants for adapters that inspect values without cloning strings, maps, JSON trees, or collection buffers.ValueContainer::ScalarandValueContainer::Collectionpreserve shape; a one-item collection remains a collection.get_or/to_orand collection variants make fallback behavior explicit: unset values can use defaults, while concrete empty first-item reads, missing collection items, type mismatches, and ordinary conversion failures remain errors.MultiValues::get_firstreads one scalar item and now requiresStrictValueRead; complete collections usegetorget_slice. This tightens source compatibility for callers that used first-item methods withVec<T>or slice targets.NamedValueandNamedMultiValuesattach a key to a runtime value without changing the value's type semantics.ValueWireV1provides a versioned, type-preserving JSON representation with boundedto_json_vec()andto_json_writer()entry points; explicitJsonDecodeLimitsandJsonEncodeLimitsare accepted by the corresponding directional_with_limitsmethods. Decode uses a caller-configuredJsonDecodeSession; encode usesJsonEncodeSessionto enforce structure and output bytes online. Use Wire V1 when the receiver must reconstruct the exactDataTypeand shape.- Natural JSON helpers produce ordinary
null, scalar, object, and array values when runtime type tags are not wanted. - The runtime vocabulary currently contains 25
DataTypevariants. Concrete feature-gated values require the corresponding feature, although their unset type declarations remain available.
This crate does not provide a complete configuration store, schema registry,
file format, or distributed cache. It provides the typed value layer those
systems can build on. Its Eq/Hash implementations are suitable for
in-memory Rust collections, not persistent fingerprints or distributed-cache
keys. The user guide documents the complete type table, JSON number contract,
errors, resource limits, and feature compatibility.
Built on Value
Two sibling crates use this value model for key-value containers:
rs-configprovides typed configuration properties, configuration-file and environment-oriented access, and policy-controlled reads. Use it when the key-value data describes application configuration.rs-metadataprovides typed metadata/property storage and filtering. Use it when values describe resources, records, or searchable application metadata.
Value::numeric_cmp compares numbers across runtime types while keeping
type-sensitive identity separate; see the numeric comparison guide.
Wire V1 or Natural JSON?
Choose Wire V1 when a receiver must distinguish Int32(42) from String("42"),
preserve scalar versus collection shape, or retain Unset(DataType). A typical
document is:
Choose to_json_value() when the boundary is ordinary application JSON and
the receiver only needs JSON semantics. Wire V1 is a closed, versioned format;
Natural JSON omits explicit runtime type and shape tags. Concrete collections
still project to arrays, including one-item collections, while scalar and
collection unset values both project to null. JSON alone cannot uniquely
recover the original runtime shape: a scalar Value::Json may itself contain
an array. Use Wire V1 when runtime type, shape, and unset declarations must be
reconstructed. Wire V1 rejects non-finite floats, unsupported
types, malformed payloads, and runtime JSON numbers that cannot round trip
with the same Number identity instead of guessing. With
serde_json/arbitrary_precision, nonstandard or over-precise number forms may
be rejected; use explicit big-number types or an application string schema
when exact precision is required.
For complete untrusted JSON documents, use the bounded Wire JSON helpers. For
embedded values, pass ValueWireV1Seed or ValueWirePayloadV1Seed to
JsonDecoder::decode_unique_seed_utf8 or next_value_seed so values in one
request share its budget; create a fresh bounded session for each independent
Wire operation. Wire DTOs implement Serialize but not generic Deserialize;
NamedValue and NamedMultiValues do implement generic Deserialize, whose
resource accounting comes from the supplied deserializer. This is not a budget
for every Serde decode. When serde_json/arbitrary_precision is unified, use a
bounded JSON helper or a decode_unique_* path. See the Wire V1 guide
for type and shape details, Instant and preflight limits, DTO seeds,
intermediate-tree tradeoffs, and feature compatibility.
A scalar treated as missing by the conversion policy may use a fallback; a missing item in a concrete collection, including its first item, does not. An explicitly empty collection also remains an error for first-item reads. See the errors and diagnostics guide and default-value troubleshooting guide.
Learn more
- English user guide
- Architecture and Wire design
- 中文用户手册
- API documentation
qubit-datatypeconversion contract- 中文 README
Testing
# Run tests with the default feature set
# Run tests with all declared features
# Project CI checks
# Check code coverage
License
Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.
Licensed under the Apache License, Version 2.0. See LICENSE for the full license text.
Contributing
Contributions are welcome. Please follow the Rust API guidelines, keep public
API documentation and tests current, and run ./.infra/bin/align-ci.sh to format code and
./.infra/bin/ci-check.sh to satisfy CI requirements before submitting a pull request.
Author
Haixing Hu - Qubit Co. Ltd.
Repository: https://github.com/qubit-ltd/rs-value