diff --git a/rayforce/src/vector.rs b/rayforce/src/vector.rs index 1660e61..a95f9d1 100644 --- a/rayforce/src/vector.rs +++ b/rayforce/src/vector.rs @@ -208,6 +208,9 @@ impl Value { /// `to_vec::>()` maps it to `None` either way. Use `set_null` to /// null an element. pub fn is_null_at(&self, idx: usize) -> bool { + if !self.is_vec() || idx >= self.len() { + return false; + } unsafe { sys::ray_vec_is_null(self.as_ptr(), idx as i64) } } @@ -220,6 +223,12 @@ impl Value { /// [`Value::is_atom_null`] is true for it and `Option` extraction /// yields `None`, while plain `String` extraction still succeeds. pub fn get(&self, idx: usize) -> Result { + if !self.is_vec() && self.type_code() != sys::RAY_LIST as i8 { + return Err(RayError::binding(format!( + "get: value is not a vector or list (type tag {})", + self.type_code() + ))); + } let n = self.len(); if idx >= n { return Err(RayError::binding(format!( diff --git a/rayforce/tests/containers.rs b/rayforce/tests/containers.rs index a38d51e..30bd9a6 100644 --- a/rayforce/tests/containers.rs +++ b/rayforce/tests/containers.rs @@ -61,6 +61,17 @@ fn vector_get_and_iter() { .unwrap(); } +#[test] +fn collection_access_rejects_atoms() { + Runtime::scope(|_rt| { + let atom = Value::i64(42); + assert!(atom.get(0).is_err()); + assert!(!atom.is_null_at(0)); + Ok(()) + }) + .unwrap(); +} + #[test] fn vector_mutation() { Runtime::scope(|_rt| {