Skip to main content

nix_bindings/
lists.rs

1use std::ptr::NonNull;
2
3use crate::{Error, Result, Value, ValueType, sys};
4
5impl Value<'_> {
6  /// Check if this value is a list.
7  ///
8  /// # Example
9  ///
10  /// ```rust,no_run
11  /// use std::sync::Arc;
12  ///
13  /// use nix_bindings::{Context, EvalStateBuilder, Store};
14  /// fn main() -> Result<(), Box<dyn std::error::Error>> {
15  ///   let ctx = Arc::new(Context::new()?);
16  ///   let store = Arc::new(Store::open(&ctx, None)?);
17  ///   let state = EvalStateBuilder::new(&store)?.build()?;
18  ///   let list = state.eval_from_string("[1 2 3]", "<eval>")?;
19  ///   assert!(list.is_list());
20  ///   Ok(())
21  /// }
22  /// ```
23  #[must_use]
24  pub fn is_list(&self) -> bool {
25    self.value_type() == ValueType::List
26  }
27
28  /// Get the length of this list.
29  ///
30  /// # Errors
31  ///
32  /// Returns an error if this value is not a list.
33  ///
34  /// # Example
35  ///
36  /// ```no_run
37  /// # use std::sync::Arc;
38  /// # use nix_bindings::{Context, EvalStateBuilder, Store};
39  /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
40  /// # let ctx = Arc::new(Context::new()?);
41  /// # let store = Arc::new(Store::open(&ctx, None)?);
42  /// # let state = EvalStateBuilder::new(&store)?.build()?;
43  /// let list = state.eval_from_string("[1 2 3]", "<eval>")?;
44  /// assert_eq!(list.list_len()?, 3);
45  /// # Ok(())
46  /// # }
47  /// ```
48  pub fn list_len(&self) -> Result<usize> {
49    if !self.is_list() {
50      return Err(Error::InvalidType {
51        expected: "list",
52        actual:   self.value_type().to_string(),
53      });
54    }
55
56    // SAFETY: context and value are valid, type is checked.
57    // nix_get_list_size returns the length as c_uint with no error code.
58    let len = unsafe {
59      sys::nix_get_list_size(self.state.context.as_ptr(), self.inner.as_ptr())
60    };
61
62    Ok(len as usize)
63  }
64
65  /// Get an element from this list by index.
66  ///
67  /// # Arguments
68  ///
69  /// * `idx` - The index of the element to retrieve (0-based)
70  ///
71  /// # Errors
72  ///
73  /// Returns an error if this value is not a list or the index is out of
74  /// bounds.
75  ///
76  /// # Example
77  ///
78  /// ```no_run
79  /// # use std::sync::Arc;
80  /// # use nix_bindings::{Context, EvalStateBuilder, Store};
81  /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
82  /// # let ctx = Arc::new(Context::new()?);
83  /// # let store = Arc::new(Store::open(&ctx, None)?);
84  /// # let state = EvalStateBuilder::new(&store)?.build()?;
85  /// let list = state.eval_from_string("[1 2 3]", "<eval>")?;
86  /// let first = list.list_get(0)?;
87  /// assert_eq!(first.as_int()?, 1);
88  /// # Ok(())
89  /// # }
90  /// ```
91  pub fn list_get(&self, idx: usize) -> Result<Value<'_>> {
92    if !self.is_list() {
93      return Err(Error::InvalidType {
94        expected: "list",
95        actual:   self.value_type().to_string(),
96      });
97    }
98
99    let len = self.list_len()?;
100    if idx >= len {
101      return Err(Error::IndexOutOfBounds {
102        index:  idx,
103        length: len,
104      });
105    }
106
107    // SAFETY: context, value, and state are valid; index is bounds-checked.
108    // nix_get_list_byidx returns a GC-owned pointer (refcount incremented for
109    // us). Value's Drop calls nix_value_decref to release our reference.
110    let elem_ptr = unsafe {
111      sys::nix_get_list_byidx(
112        self.state.context.as_ptr(),
113        self.inner.as_ptr(),
114        self.state.as_ptr(),
115        idx as std::os::raw::c_uint,
116      )
117    };
118
119    let inner = NonNull::new(elem_ptr).ok_or(Error::NullPointer)?;
120    Ok(Value {
121      inner,
122      state: self.state,
123    })
124  }
125
126  /// Create an iterator over the elements of this list.
127  ///
128  /// # Errors
129  ///
130  /// Returns an error if this value is not a list.
131  pub fn list_iter(&self) -> Result<ListIterator<'_>> {
132    if !self.is_list() {
133      return Err(Error::InvalidType {
134        expected: "list",
135        actual:   self.value_type().to_string(),
136      });
137    }
138
139    let len = self.list_len()?;
140    Ok(ListIterator {
141      value:  self,
142      index:  0,
143      length: len,
144    })
145  }
146}
147
148/// Iterator over elements in a Nix list.
149///
150/// This struct is created by [`Value::list_iter`] and is used to iterate
151/// over the elements of a Nix list.
152#[derive(Debug)]
153pub struct ListIterator<'a> {
154  value:  &'a Value<'a>,
155  index:  usize,
156  length: usize,
157}
158
159impl<'a> Iterator for ListIterator<'a> {
160  type Item = Result<Value<'a>>;
161
162  fn next(&mut self) -> Option<Self::Item> {
163    if self.index >= self.length {
164      return None;
165    }
166
167    let idx = self.index;
168    self.index += 1;
169
170    // Bypass list_get's redundant type+length check; we already know the
171    // value is a list and idx < length.
172    // SAFETY: context, value, and state are valid; idx is bounds-checked.
173    let elem_ptr = unsafe {
174      sys::nix_get_list_byidx(
175        self.value.state.context.as_ptr(),
176        self.value.inner.as_ptr(),
177        self.value.state.as_ptr(),
178        idx as std::os::raw::c_uint,
179      )
180    };
181
182    match NonNull::new(elem_ptr) {
183      Some(inner) => {
184        Some(Ok(Value {
185          inner,
186          state: self.value.state,
187        }))
188      },
189      None => Some(Err(Error::NullPointer)),
190    }
191  }
192
193  fn size_hint(&self) -> (usize, Option<usize>) {
194    let remaining = self.length - self.index;
195    (remaining, Some(remaining))
196  }
197}
198
199impl ExactSizeIterator for ListIterator<'_> {}
200
201#[cfg(test)]
202mod tests {
203  use std::sync::Arc;
204
205  use serial_test::serial;
206
207  use super::*;
208  use crate::{Context, EvalStateBuilder, Store};
209
210  #[test]
211  #[serial]
212  fn test_is_list() {
213    let ctx = Arc::new(Context::new().expect("Failed to create context"));
214    let store =
215      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
216    let state = EvalStateBuilder::new(&store)
217      .expect("Failed to create builder")
218      .build()
219      .expect("Failed to build state");
220
221    let list = state
222      .eval_from_string("[1 2 3]", "<eval>")
223      .expect("Failed to evaluate list");
224    assert!(list.is_list());
225
226    let int = state
227      .eval_from_string("1", "<eval>")
228      .expect("Failed to evaluate int");
229    assert!(!int.is_list());
230  }
231
232  #[test]
233  #[serial]
234  fn test_list_len() {
235    let ctx = Arc::new(Context::new().expect("Failed to create context"));
236    let store =
237      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
238    let state = EvalStateBuilder::new(&store)
239      .expect("Failed to create builder")
240      .build()
241      .expect("Failed to build state");
242
243    let empty = state
244      .eval_from_string("[]", "<eval>")
245      .expect("Failed to evaluate empty list");
246    assert_eq!(empty.list_len().expect("Failed to get list length"), 0);
247
248    let list = state
249      .eval_from_string("[1 2 3]", "<eval>")
250      .expect("Failed to evaluate list");
251    assert_eq!(list.list_len().expect("Failed to get list length"), 3);
252  }
253
254  #[test]
255  #[serial]
256  fn test_list_get() {
257    let ctx = Arc::new(Context::new().expect("Failed to create context"));
258    let store =
259      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
260    let state = EvalStateBuilder::new(&store)
261      .expect("Failed to create builder")
262      .build()
263      .expect("Failed to build state");
264
265    let list = state
266      .eval_from_string("[10 20 30]", "<eval>")
267      .expect("Failed to evaluate list");
268
269    let first = list.list_get(0).expect("Failed to get first element");
270    assert_eq!(first.as_int().expect("Failed to get int"), 10);
271
272    let second = list.list_get(1).expect("Failed to get second element");
273    assert_eq!(second.as_int().expect("Failed to get int"), 20);
274
275    let third = list.list_get(2).expect("Failed to get third element");
276    assert_eq!(third.as_int().expect("Failed to get int"), 30);
277
278    // Out of bounds should return IndexOutOfBounds error
279    let result = list.list_get(5);
280    assert!(matches!(
281      result,
282      Err(Error::IndexOutOfBounds {
283        index:  5,
284        length: 3,
285      })
286    ));
287  }
288
289  #[test]
290  #[serial]
291  fn test_list_iter() {
292    let ctx = Arc::new(Context::new().expect("Failed to create context"));
293    let store =
294      Arc::new(Store::open(&ctx, None).expect("Failed to open store"));
295    let state = EvalStateBuilder::new(&store)
296      .expect("Failed to create builder")
297      .build()
298      .expect("Failed to build state");
299
300    let list = state
301      .eval_from_string("[1 2 3]", "<eval>")
302      .expect("Failed to evaluate list");
303
304    let mut iter = list.list_iter().expect("Failed to create iterator");
305    assert_eq!(iter.len(), 3);
306
307    let first = iter
308      .next()
309      .expect("Failed to get first")
310      .expect("Failed to get first value");
311    assert_eq!(first.as_int().expect("Failed to get int"), 1);
312
313    let second = iter
314      .next()
315      .expect("Failed to get second")
316      .expect("Failed to get second value");
317    assert_eq!(second.as_int().expect("Failed to get int"), 2);
318
319    let third = iter
320      .next()
321      .expect("Failed to get third")
322      .expect("Failed to get third value");
323    assert_eq!(third.as_int().expect("Failed to get int"), 3);
324
325    assert!(iter.next().is_none());
326  }
327}