Skip to main content

Value

Struct Value 

Source
pub struct Value(/* private fields */);
Expand description

A runtime value, packed into a single 64-bit word by NaN-boxing.

A dynamic interpreter spends most of its time moving values between the stack, locals, and the operand of an instruction. Representing each one as a Rust enum costs sixteen bytes (a tag word plus the widest payload) and a branch on every copy. NaN-boxing folds the kind and the payload into the bit pattern of one f64-sized word, so a Value is Copy, eight bytes wide, and needs no discriminant alongside it.

The trick is that IEEE-754 leaves a large block of bit patterns unused: every quiet-NaN encoding names the same abstract “not a number”. A Value stores a real f64 as itself, and hides every other kind — nil, booleans, 32-bit integers, and interned Symbols — inside quiet-NaN payloads that no genuine float ever produces. Reading a value back is a mask and a compare; see unpack.

The encoding is entirely safe: it is built from f64::to_bits and f64::from_bits and integer arithmetic, with no pointers and no unsafe. Because it never boxes a pointer, it carries no heap data — strings and other interned identities travel as Symbol handles, resolved elsewhere against the interner that issued them.

§Equality

Equality follows IEEE-754 for floats and identity for everything else: two Float values compare with f64 semantics, so NaN != NaN and 0.0 == -0.0; all other kinds (including a Float against a non-Float) compare by bit pattern. Because NaN != NaN, Value deliberately does not implement Eq or Hash; if you need a hashable key, branch on unpack and hash the parts, or use bits when you have separately ensured no float is NaN.

§Examples

use value_lang::{Unpacked, Value};

let answer = Value::int(42);
let ratio = Value::float(0.375);

assert!(answer.is_int());
assert_eq!(answer.as_int(), Some(42));
assert_eq!(ratio.as_float(), Some(0.375));

// Branch on the kind with `unpack`.
match answer.unpack() {
    Unpacked::Int(n) => assert_eq!(n, 42),
    _ => unreachable!(),
}

// A `Value` is eight bytes and `Copy`.
assert_eq!(core::mem::size_of::<Value>(), 8);

Implementations§

Source§

impl Value

Source

pub const fn nil() -> Self

The unit value, nil — the absence of any other value.

This is what an interpreter yields for an expression with no result, an uninitialised local, or a missing map entry. It is also Value::default.

§Examples
use value_lang::Value;

let v = Value::nil();
assert!(v.is_nil());
assert_eq!(v, Value::default());
Source

pub const fn bool(b: bool) -> Self

A boolean value.

§Examples
use value_lang::Value;

assert_eq!(Value::bool(true).as_bool(), Some(true));
assert_eq!(Value::bool(false).as_bool(), Some(false));
Source

pub const fn int(n: i32) -> Self

A 32-bit signed integer.

Integers are stored as an i32 because that is what fits losslessly beside the tag in a NaN-box payload; use float when you need the full magnitude and precision range of a double.

§Examples
use value_lang::Value;

assert_eq!(Value::int(-7).as_int(), Some(-7));
assert_eq!(Value::int(i32::MAX).as_int(), Some(i32::MAX));
Source

pub fn float(f: f64) -> Self

A floating-point value.

Any finite double, both infinities, and NaN are accepted. Every NaN is stored as one canonical bit pattern, so a round trip through Value normalises NaN payloads (the value is still NaN, and still compares unequal to itself).

§Examples
use value_lang::Value;

assert_eq!(Value::float(2.5).as_float(), Some(2.5));
assert_eq!(Value::float(f64::INFINITY).as_float(), Some(f64::INFINITY));
assert!(Value::float(f64::NAN).as_float().unwrap().is_nan());
Source

pub fn sym(s: Symbol) -> Self

An interned Symbol — a compact handle for a string or identifier.

The symbol’s 32-bit id is packed directly into the value. It is only meaningful with the interner that issued it; Value stores the handle, not the bytes.

§Examples
use intern_lang::Interner;
use value_lang::Value;

let mut interner = Interner::new();
let name = interner.intern("total");

let v = Value::sym(name);
assert_eq!(v.as_sym(), Some(name));
assert_eq!(interner.resolve(v.as_sym().unwrap()), Some("total"));
Source

pub fn is_nil(self) -> bool

Returns true when this value is nil.

Source

pub fn is_bool(self) -> bool

Returns true when this value is a boolean.

Source

pub fn is_int(self) -> bool

Returns true when this value is a 32-bit integer.

Source

pub fn is_float(self) -> bool

Returns true when this value is a float.

Every value that is not a boxed kind is a float, including the infinities and NaN.

Source

pub fn is_sym(self) -> bool

Returns true when this value is an interned Symbol.

Source

pub fn as_bool(self) -> Option<bool>

Returns the boolean, or None if this value is not a boolean.

Source

pub fn as_int(self) -> Option<i32>

Returns the integer, or None if this value is not an integer.

Source

pub fn as_float(self) -> Option<f64>

Returns the float, or None if this value is not a float.

This does not convert an int to a float; it returns None for every non-float kind. Convert explicitly if you want coercion.

Source

pub fn as_sym(self) -> Option<Symbol>

Returns the interned Symbol, or None if this value is not a symbol.

Source

pub const fn bits(self) -> u64

Returns the raw 64-bit NaN-box encoding.

This is the exact bit pattern the value occupies. It is stable for a given value and useful for building a custom hash or a compact serialization, but note that two NaN floats share one canonical pattern and 0.0/-0.0 do not — so raw bits are identity, not numeric equality.

Source

pub fn unpack(self) -> Unpacked

Expands this value into its Unpacked tagged-union form for matching.

This is the reverse of Value::from(unpacked). Use it when you need to branch on every kind at once rather than test one kind with an as_* accessor.

§Examples
use value_lang::{Unpacked, Value};

fn describe(v: Value) -> &'static str {
    match v.unpack() {
        Unpacked::Nil => "nil",
        Unpacked::Bool(_) => "bool",
        Unpacked::Int(_) => "int",
        Unpacked::Float(_) => "float",
        Unpacked::Sym(_) => "sym",
    }
}

assert_eq!(describe(Value::int(1)), "int");
assert_eq!(describe(Value::nil()), "nil");

Trait Implementations§

Source§

impl Clone for Value

Source§

fn clone(&self) -> Value

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for Value

Source§

impl Debug for Value

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Value

Source§

fn default() -> Self

The default value is nil.

Source§

impl<'de> Deserialize<'de> for Value

Available on crate feature serde only.
Source§

fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl From<Symbol> for Value

Source§

fn from(s: Symbol) -> Self

Converts to this type from the input type.
Source§

impl From<Unpacked> for Value

Source§

fn from(u: Unpacked) -> Self

Converts to this type from the input type.
Source§

impl From<bool> for Value

Source§

fn from(b: bool) -> Self

Converts to this type from the input type.
Source§

impl From<f64> for Value

Source§

fn from(f: f64) -> Self

Converts to this type from the input type.
Source§

impl From<i32> for Value

Source§

fn from(n: i32) -> Self

Converts to this type from the input type.
Source§

impl PartialEq for Value

Source§

fn eq(&self, other: &Self) -> bool

See the type-level note on equality: floats compare with f64 semantics, everything else by bit pattern.

1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl Serialize for Value

Available on crate feature serde only.
Source§

fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer,

Serialize this value into the given Serde serializer. Read more

Auto Trait Implementations§

§

impl Freeze for Value

§

impl RefUnwindSafe for Value

§

impl Send for Value

§

impl Sync for Value

§

impl Unpin for Value

§

impl UnsafeUnpin for Value

§

impl UnwindSafe for Value

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.