Skip to main content

value_lang/
lib.rs

1//! # value_lang
2//!
3//! Compact runtime value representation for interpreted languages, by NaN-boxing.
4//!
5//! A dynamically-typed interpreter needs one type to stand for every runtime
6//! value — nil, a boolean, a number, an interned name — and it copies that type on
7//! nearly every instruction. `value-lang` packs all of those into a single 64-bit
8//! [`Value`]: eight bytes, [`Copy`], and free of any discriminant word, because the
9//! kind is folded into the bit pattern itself. The technique is *NaN-boxing* — real
10//! floats are stored as themselves, and every other kind hides inside the quiet-NaN
11//! encodings that no genuine float produces.
12//!
13//! The whole representation is safe Rust: it is built from [`f64::to_bits`] and
14//! [`f64::from_bits`] and integer arithmetic, with no pointers and no `unsafe`
15//! (`unsafe` is forbidden crate-wide). It owns value representation and nothing
16//! else — strings and identifiers travel as [`Symbol`] handles from
17//! [`intern-lang`](intern_lang), resolved against the interner that issued them.
18//!
19//! ## At a glance
20//!
21//! - [`Value`] — the eight-byte NaN-boxed handle. Build one with
22//!   [`nil`](Value::nil), [`bool`](Value::bool), [`int`](Value::int),
23//!   [`float`](Value::float), or [`sym`](Value::sym); test it with the `is_*`
24//!   predicates; read it back with the `as_*` accessors.
25//! - [`Unpacked`] — the tagged-union view. Call [`Value::unpack`] to `match` on
26//!   every kind at once, and [`Value::from`] to pack one back.
27//! - [`Symbol`] — re-exported from `intern-lang`; the compact string handle a
28//!   `Value` can carry.
29//!
30//! ## Example
31//!
32//! ```
33//! use value_lang::{Unpacked, Value};
34//!
35//! // One eight-byte type for every runtime value.
36//! let stack = [Value::int(2), Value::float(0.5), Value::bool(true), Value::nil()];
37//!
38//! // Dispatch on the kind by unpacking.
39//! let ints: i32 = stack
40//!     .iter()
41//!     .filter_map(|v| v.as_int())
42//!     .sum();
43//! assert_eq!(ints, 2);
44//!
45//! assert_eq!(stack[1].unpack(), Unpacked::Float(0.5));
46//! assert_eq!(core::mem::size_of::<Value>(), 8);
47//! ```
48//!
49//! ## `no_std`
50//!
51//! The crate is `no_std`-compatible and does not even require `alloc`: the
52//! representation is pure integer and float arithmetic. The default `std` feature
53//! is additive and only forwards `std` to `intern-lang`. Disable default features
54//! to build for a bare target. The optional `serde` feature adds `Serialize` /
55//! `Deserialize` for [`Value`].
56
57#![cfg_attr(not(feature = "std"), no_std)]
58#![cfg_attr(docsrs, feature(doc_cfg))]
59#![forbid(unsafe_code)]
60#![deny(missing_docs)]
61#![deny(unused_must_use)]
62#![deny(unused_results)]
63#![deny(clippy::unwrap_used)]
64#![deny(clippy::expect_used)]
65#![deny(clippy::todo)]
66#![deny(clippy::unimplemented)]
67#![deny(clippy::print_stdout)]
68#![deny(clippy::print_stderr)]
69#![deny(clippy::dbg_macro)]
70#![deny(clippy::unreachable)]
71
72mod value;
73
74#[cfg(feature = "serde")]
75mod serde_impl;
76
77pub use intern_lang::Symbol;
78pub use value::{Unpacked, Value};