Skip to content

Vectors

A vector is a homogeneous, contiguous buffer of one element type — the workhorse of a columnar engine. Rayforce vectors live in engine-owned memory, and the Rust bindings give you zero-copy access to that memory wherever possible. This is the page to read if you care about performance.

Assume a live runtime

use rayforce::{Runtime, Value};
let _rt = Runtime::new()?;

The numeric element types are captured by the VecElem trait: u8, i16, i32, i64, f32, f64. (Booleans, symbols, strings, and temporals have their own constructors — see Boolean, Symbol, String, Temporal.)

Construction — a single memcpy

Value::vec(&[T]) builds a vector with one bulk copy of the whole slice, not an element-by-element loop.

let v = Value::vec(&[1i64, 2, 3, 4, 5]);
assert_eq!(v.len(), 5);

Annotate the element type

A bare integer literal is i32, and a bare float literal is f64. For other widths annotate the first element: Value::vec(&[1i64, 2, 3]), Value::vec(&[1.5f32, 2.5]).

Zero-copy reads — as_slice

as_slice::<T>() borrows the engine buffer directly as &[T]; nothing is copied. The element type is checked at the boundary, so a mismatch is an error rather than a reinterpretation of bytes.

let v = Value::vec(&[1i64, 2, 3, 4, 5]);
assert_eq!(v.as_slice::<i64>()?, &[1, 2, 3, 4, 5]);
assert!(v.as_slice::<i32>().is_err()); // wrong element type rejected

Boxed access — get, iter, to_vec

When you want owned values or per-element Values:

let v = Value::vec(&[10i64, 20, 30]);

assert_eq!(v.get(0)?.as_i64()?, 10);
assert!(v.get(3).is_err()); // out of range

let owned: Vec<i64> = v.to_vec()?;             // FromValue per element
assert_eq!(owned, vec![10, 20, 30]);

let via_iter: Vec<i64> =
    v.iter().map(|r| r.unwrap().as_i64().unwrap()).collect();
assert_eq!(via_iter, vec![10, 20, 30]);

Prefer as_slice for numeric scans; reach for get/iter/to_vec when you need Values or a Vec<T>.

Mutation — set, push

let mut v = Value::vec(&[1i64, 2, 3]);
v.set(1, 99i64)?;
assert_eq!(v.as_slice::<i64>()?, &[1, 99, 3]);

v.push(4i64)?;
assert_eq!(v.as_slice::<i64>()?, &[1, 99, 3, 4]);

Both are type-checked, and an out-of-range set fails without corrupting or leaking the vector:

let mut v = Value::vec(&[1i64, 2, 3]);
assert!(v.set(0, 1.0f64).is_err()); // wrong element type
assert!(v.set(5, 9i64).is_err());   // out of range
assert_eq!(v.as_slice::<i64>()?, &[1, 2, 3]); // unchanged

The i64 literal-suffix gotcha

set and push infer the element type from the literal you pass. On an I64 vector, v.push(4) passes an i32 and fails the type check — you must write v.push(4i64) and v.set(1, 99i64). The same applies to i16, f32, etc.

Slicing and concatenation

let v = Value::vec(&[1i64, 2, 3, 4, 5]);
let s = v.slice(1, 3)?; // offset 1, length 3
assert_eq!(s.as_slice::<i64>()?, &[2, 3, 4]);

let a = Value::vec(&[1i64, 2]);
let b = Value::vec(&[3i64, 4]);
let c = a.concat(&b)?;
assert_eq!(c.as_slice::<i64>()?, &[1, 2, 3, 4]);

Null bitmap

Vectors track nulls in a separate bitmap attribute, not by scanning payload bytes. A buffer that coincidentally contains a sentinel value (e.g. i64::MIN) is not considered null until the element is explicitly marked.

// A coincidental sentinel is NOT null on its own:
let raw = Value::vec(&[1i64, i64::MIN, 3]);
assert!(!raw.is_null_at(1));

// Explicitly marking an element is the supported path:
let mut v = Value::vec(&[1i64, 2, 3]);
v.set_null(1, true)?;
assert!(v.is_null_at(1));
assert!(v.get(1)?.is_null());
assert_eq!(v.get(0)?.as_i64()?, 1); // neighbors untouched

Why the bitmap matters

Decoupling nulls from payload bytes lets the engine store any in-band value without ambiguity and lets a column be marked null in O(1) without touching the data. Test for nulls with is_null_at(idx) rather than comparing against a sentinel.

Constructed vectors match the engine

use rayforce::eval;
let v = Value::vec(&[0i64, 1, 2, 3, 4]);
assert_eq!(v.format(), eval("(til 5)")?.format()); // "0 1 2 3 4"

For heterogeneous collections, see Lists; for keyed data, see Dicts.