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:
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.11", = ["converter"] }
= { = "0.13", = 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.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.
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 closed and versioned;
Natural JSON intentionally omits runtime type tags. The user guide contains the
full Wire workflow, borrowed payload examples, feature compatibility rules,
and resource-limit handling.
The Wire DTOs implement Serialize, but deliberately do not implement generic
Deserialize: a general Serde deserializer cannot enforce the raw-input and
structural limits required at an untrusted boundary. Use the bounded
ValueWireV1::decode_json_slice helpers for complete JSON documents. For an
embedded value, pass ValueWireV1Seed::new() or ValueWirePayloadV1Seed::new()
to the surrounding JsonDecoder::decode_seed_utf8 or next_value_seed call so
the outer protocol owns one shared budget.
NamedValue and NamedMultiValues do implement generic Deserialize so they
can be embedded in larger Serde documents. That implementation validates the
V1 schema and payload shape but inherits resource accounting from the supplied
deserializer. Use their bounded decode_json_slice helpers for complete,
untrusted JSON input, or a resource-bounded outer decoder when they are nested.
Natural JSON cannot reconstruct DataType, unset state, or scalar-versus-
collection shape. Wire V1 rejects non-finite floats and unsupported or malformed
payloads instead of guessing. Use a fresh bounded session for each independent
Wire operation; reuse a session only when several embedded values belong to the
same outer request budget.
For conversion fallbacks, a policy-missing scalar may use the supplied default. A missing item inside a concrete collection never does, including item zero; an explicitly empty collection also remains an error for first-item reads.
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 ./align-ci.sh to format code and
./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