# Qubit Value
[](https://github.com/qubit-ltd/rs-value/actions/workflows/ci.yml)
[](https://qubit-ltd.github.io/rs-value/coverage/)
[](https://crates.io/crates/qubit-value)
[](https://www.rust-lang.org)
[](LICENSE)
[](README.zh_CN.md)
`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 `null` are 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.
```rust
use std::collections::HashMap;
use std::time::Duration;
use qubit_datatype::DataType;
use qubit_value::Value;
let config = HashMap::from([
("host".to_owned(), Value::new("localhost".to_owned())),
("port".to_owned(), Value::new("8080".to_owned())),
("debug".to_owned(), Value::new(false)),
(
"timeout".to_owned(),
Value::new_unset(DataType::Duration),
),
]);
let host: String = config["host"].get()?;
assert_eq!(host, "localhost");
let port: u16 = config["port"].to()?;
assert_eq!(port, 8080);
let debug: bool = config["debug"].get()?;
assert!(!debug);
let timeout: Duration = config["timeout"].get_or(Duration::from_secs(30))?;
assert_eq!(timeout, Duration::from_secs(30));
```
If you need a complete, general-purpose configuration object instead of
assembling a map yourself, use `Config` from
[`rs-config`](https://github.com/qubit-ltd/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.
## Installation
Add the core crate and its type vocabulary to `Cargo.toml`:
```toml
[dependencies]
qubit-value = "0.10"
qubit-datatype = { version = "0.10", default-features = false }
```
The default feature set is empty. Enable only the families you use:
| `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 JSON Wire decoding; Natural JSON also requires `converter` |
| `redact` | Policy-aware redacted views through `qubit-redact` |
| `all` | `converter`, `chrono`, `big-number`, `url`, `json`, and `redact` |
## Supported `DataType` values
`qubit_datatype::DataType` is the closed runtime type vocabulary used by
`Value`, `MultiValues`, and `Unset`. The same type identifies a scalar and its
homogeneous collection form.
| `Bool` | `bool` | — | flags and switches |
| `Char` | `char` | — | one Unicode character |
| `Int8` / `Int16` / `Int32` / `Int64` / `Int128` | `i8` … `i128` | — | signed integers |
| `UInt8` / `UInt16` / `UInt32` / `UInt64` / `UInt128` | `u8` … `u128` | — | unsigned integers |
| `Float32` / `Float64` | `f32` / `f64` | — | finite or in-memory floating-point values |
| `String` | `String` | — | text and text-backed input |
| `Date` | `chrono::NaiveDate` | `chrono` | calendar date |
| `Time` | `chrono::NaiveTime` | `chrono` | time of day |
| `DateTime` | `chrono::NaiveDateTime` | `chrono` | date and local time |
| `Instant` | `chrono::DateTime<chrono::Utc>` | `chrono` | UTC time point |
| `BigInteger` | `num_bigint::BigInt` | `big-integer` | arbitrary-precision integer |
| `BigDecimal` | `bigdecimal::BigDecimal` | `big-decimal` | arbitrary-precision decimal |
| `Duration` | `std::time::Duration` | — | elapsed time |
| `Url` | `url::Url` | `url` | parsed URL |
| `StringMap` | `HashMap<String, String>` | — | string-key/string-value properties |
| `Json` | `serde_json::Value` | `json` | arbitrary JSON structure |
The 25 variants above are the complete current `DataType` enum. A feature-gated
variant can still appear in an `Unset(DataType)` declaration, but a concrete
value of that type requires the corresponding feature in the build that stores
or reads it.
## What it provides
- `Value` and `MultiValues` have typed constructors, typed getters, generic
mutation, borrowed reads, and explicit unset state.
- `ValueContainer::Scalar` and `ValueContainer::Collection` preserve shape;
a one-item collection remains a collection.
- `get_or`/`to_or` and collection variants make fallback behavior explicit:
unset values can use defaults, while type mismatches and ordinary conversion
failures are still reported.
- `NamedValue` and `NamedMultiValues` attach a key to a runtime value without
changing the value's type semantics.
- `ValueWireV1` provides a versioned, type-preserving JSON representation.
Use it when the receiver must reconstruct the exact `DataType` and shape.
- Natural JSON helpers produce ordinary `null`, scalar, object, and array
values when runtime type tags are not wanted.
The 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.
## Built on `Value`
Two sibling crates use this value model for key-value containers:
- [`rs-config`](https://github.com/qubit-ltd/rs-config) provides typed
configuration properties, configuration-file and environment-oriented
access, and policy-controlled reads. Use it when the key-value data describes
application configuration.
- [`rs-metadata`](https://github.com/qubit-ltd/rs-metadata) provides 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:
```json
{"version":1,"value":{"scalar":{"int32":42}}}
```
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.
For example, Natural JSON produces these exact JSON strings:
```rust
use std::collections::HashMap;
use qubit_datatype::DataType;
use qubit_value::{MultiValues, Value};
assert_eq!(Value::new(42i32).to_json_value()?.to_string(), "42");
assert_eq!(
Value::new("localhost".to_owned())
.to_json_value()?
.to_string(),
r#""localhost""#,
);
assert_eq!(
Value::new_unset(DataType::String)
.to_json_value()?
.to_string(),
"null",
);
assert_eq!(
MultiValues::new([8080i32, 8081])
.to_json_value()?
.to_string(),
"[8080,8081]",
);
assert_eq!(
Value::new(HashMap::from([
("z".to_owned(), "26".to_owned()),
("a".to_owned(), "1".to_owned()),
]))
.to_json_value()?
.to_string(),
r#"{"a":"1","z":"26"}"#,
);
```
The scalar number becomes `42`, the string keeps its JSON quotes, an unset
value becomes `null`, a concrete collection becomes an array, and string-map
keys are emitted in dictionary order.
## Learn more
- [English user guide](doc/user_guide.md)
- [中文用户手册](doc/user_guide.zh_CN.md)
- [API documentation](https://docs.rs/qubit-value)
- [`qubit-datatype` conversion contract](https://docs.rs/qubit-datatype/latest/qubit_datatype/)
- [中文 README](README.zh_CN.md)
## Testing
```bash
# Run tests with the default feature set
cargo test
# Run tests with all declared features
cargo test --all-features
# Project CI checks
./ci-check.sh
# Check code coverage
./coverage.sh
```
## License
Copyright (c) 2025 - 2026. Haixing Hu. All rights reserved.
Licensed under the Apache License, Version 2.0. See [LICENSE](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](https://github.com/qubit-ltd/rs-value)