Skip to main content

igraph/
strvector.rs

1//! Owned vectors of strings (`igraph_strvector_t`, `igraph_strvector.h`).
2//!
3//! igraph uses string vectors for vertex names, file formats and string
4//! attributes. [`StrVector`] owns a copy of every string, is indexed with
5//! `sv[i]` (a `&str`), and offers the usual editing operations (`push`,
6//! `set`, `remove`, `pop`, `swap`, `select`, `append`, `extend_from`,
7//! `resize`, `truncate`, `clear`, ...). Strings are stored as C strings: an
8//! interior NUL byte truncates a string when it is read back.
9//!
10//! ```
11//! use igraph::prelude::*;
12//!
13//! let mut names: StrVector = ["alice", "bob"].into_iter().collect();
14//! names.push("carol");
15//! assert_eq!(names.len(), 3);
16//! assert_eq!(names.get(1), Some("bob"));
17//! assert_eq!(names.to_vec(), vec!["alice", "bob", "carol"]);
18//! ```
19//!
20//! # Not wrapped
21//!
22//! A few functions of `igraph_strvector.h` have no direct counterpart:
23//! `igraph_strvector_set` and `igraph_strvector_push_back` take
24//! NUL-terminated C strings, while a Rust `&str` has no terminator, so
25//! [`set`](StrVector::set) and [`push`](StrVector::push) call the
26//! length-taking `igraph_strvector_set_len` and
27//! `igraph_strvector_push_back_len` instead; `igraph_strvector_swap`
28//! (exchanging two whole vectors) is [`std::mem::swap`]; and
29//! `igraph_strvector_print` / `igraph_strvector_fprint` are covered by the
30//! [`Display`](std::fmt::Display) implementation, which prints the list as
31//! `["alice", "bob"]` (igraph's one-string-per-line format is
32//! `sv.iter().collect::<Vec<_>>().join("\n")`).
33
34use crate::ffi::*;
35use std::{ffi::CStr, fmt, mem::MaybeUninit};
36
37/// Owned vector of strings (`igraph_strvector_t`).
38pub type StrVector = igraph_strvector_t;
39
40impl igraph_strvector_t {
41    /// Creates an empty string vector
42    /// ([`igraph_strvector_init`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_init)).
43    pub fn new() -> Self {
44        crate::error::ensure_init();
45        let mut raw = MaybeUninit::<Self>::uninit();
46        crate::error::check(unsafe { igraph_strvector_init(raw.as_mut_ptr(), 0) })
47            .expect("igraph failed to allocate a string vector");
48        unsafe { raw.assume_init() }
49    }
50
51    /// Number of strings
52    /// ([`igraph_strvector_size`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_size)).
53    pub fn len(&self) -> usize {
54        unsafe { igraph_strvector_size(self) as usize }
55    }
56
57    /// Whether there are no strings.
58    pub fn is_empty(&self) -> bool {
59        self.len() == 0
60    }
61
62    /// The string at `index`, or `None` if `index` is out of bounds or the
63    /// string is not valid UTF-8 (use [`get_cstr`](Self::get_cstr), or the
64    /// lossy [`iter`](Self::iter) and [`to_vec`](Self::to_vec), for such
65    /// strings) ([`igraph_strvector_get`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_get)).
66    pub fn get(&self, index: usize) -> Option<&str> {
67        self.get_cstr(index).and_then(|s| s.to_str().ok())
68    }
69
70    /// The raw C string at `index`, if any, borrowed from the vector
71    /// ([`igraph_strvector_get`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_get)).
72    pub fn get_cstr(&self, index: usize) -> Option<&CStr> {
73        if index >= self.len() {
74            return None;
75        }
76        let ptr = unsafe { igraph_strvector_get(self, index as igraph_int_t) };
77        (!ptr.is_null()).then(|| unsafe { CStr::from_ptr(ptr) })
78    }
79
80    /// Appends a copy of `value` (interior NUL bytes truncate it when read
81    /// back) ([`igraph_strvector_push_back_len`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_push_back_len)).
82    pub fn push(&mut self, value: &str) {
83        crate::error::check(unsafe {
84            igraph_strvector_push_back_len(self, value.as_ptr().cast(), value.len())
85        })
86        .expect("igraph failed to grow a string vector");
87    }
88
89    /// Iterates over the strings (lossily converted to UTF-8).
90    pub fn iter(&self) -> impl Iterator<Item = String> + '_ {
91        (0..self.len()).map(move |i| {
92            self.get_cstr(i)
93                .map(|s| s.to_string_lossy().into_owned())
94                .unwrap_or_default()
95        })
96    }
97
98    /// Copies the strings into a `Vec<String>`.
99    pub fn to_vec(&self) -> Vec<String> {
100        self.iter().collect()
101    }
102}
103
104impl Drop for igraph_strvector_t {
105    fn drop(&mut self) {
106        if !self.stor_begin.is_null() {
107            unsafe { igraph_strvector_destroy(self) };
108            self.stor_begin = std::ptr::null_mut();
109        }
110    }
111}
112
113impl Default for igraph_strvector_t {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl Clone for igraph_strvector_t {
120    fn clone(&self) -> Self {
121        crate::error::ensure_init();
122        let mut raw = MaybeUninit::<Self>::uninit();
123        crate::error::check(unsafe { igraph_strvector_init_copy(raw.as_mut_ptr(), self) })
124            .expect("igraph failed to copy a string vector");
125        unsafe { raw.assume_init() }
126    }
127}
128
129impl PartialEq for igraph_strvector_t {
130    fn eq(&self, other: &Self) -> bool {
131        self.len() == other.len() && (0..self.len()).all(|i| self.get_cstr(i) == other.get_cstr(i))
132    }
133}
134
135impl fmt::Display for igraph_strvector_t {
136    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137        f.debug_list().entries(self.iter()).finish()
138    }
139}
140
141impl<S: AsRef<str>> FromIterator<S> for igraph_strvector_t {
142    fn from_iter<I: IntoIterator<Item = S>>(iter: I) -> Self {
143        let mut sv = Self::new();
144        for s in iter {
145            sv.push(s.as_ref());
146        }
147        sv
148    }
149}
150
151impl<S: AsRef<str>> From<&[S]> for igraph_strvector_t {
152    fn from(items: &[S]) -> Self {
153        items.iter().collect()
154    }
155}
156
157impl From<&igraph_strvector_t> for Vec<String> {
158    fn from(sv: &igraph_strvector_t) -> Self {
159        sv.to_vec()
160    }
161}
162
163unsafe impl Send for igraph_strvector_t {}
164unsafe impl Sync for igraph_strvector_t {}
165
166impl igraph_strvector_t {
167    /// Creates a vector of `len` empty strings
168    /// ([`igraph_strvector_init`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_init)).
169    pub fn with_len(len: usize) -> Self {
170        crate::error::ensure_init();
171        let mut raw = MaybeUninit::<Self>::uninit();
172        crate::error::check(unsafe {
173            igraph_strvector_init(raw.as_mut_ptr(), crate::error::int_size(len))
174        })
175        .expect("igraph failed to allocate a string vector");
176        unsafe { raw.assume_init() }
177    }
178
179    /// Replaces the string at `index` with a copy of `value`
180    /// ([`igraph_strvector_set_len`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_set_len)).
181    ///
182    /// ```
183    /// use igraph::prelude::*;
184    /// let mut names = StrVector::with_len(3);
185    /// names.set(1, "hub").unwrap();
186    /// assert_eq!(names.to_vec(), vec!["", "hub", ""]);
187    /// assert_eq!(&names[1], "hub");
188    /// assert!(names.set(3, "nope").is_err());
189    /// ```
190    ///
191    /// # Errors
192    /// [`ErrorKind::InvalidValue`](crate::error::ErrorKind::InvalidValue) if
193    /// `index` is out of bounds.
194    pub fn set(&mut self, index: usize, value: &str) -> crate::error::Result<()> {
195        if index >= self.len() {
196            return Err(crate::error::Error::invalid(format!(
197                "string index {index} out of bounds (len {})",
198                self.len()
199            )));
200        }
201        crate::igraph_call!(igraph_strvector_set_len(
202            self,
203            index as igraph_int_t,
204            value.as_ptr().cast(),
205            value.len()
206        ))
207    }
208
209    /// Removes the string at `index` and returns it (lossily converted to
210    /// UTF-8) ([`igraph_strvector_remove`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_remove)).
211    ///
212    /// # Panics
213    /// If `index` is out of bounds.
214    pub fn remove(&mut self, index: usize) -> String {
215        let len = self.len();
216        assert!(
217            index < len,
218            "string index {index} out of bounds (len {len})"
219        );
220        let value = self
221            .get_cstr(index)
222            .map(|s| s.to_string_lossy().into_owned())
223            .unwrap_or_default();
224        unsafe { igraph_strvector_remove(self, index as igraph_int_t) };
225        value
226    }
227
228    /// Removes and returns the last string, if any.
229    pub fn pop(&mut self) -> Option<String> {
230        let len = self.len();
231        (len > 0).then(|| self.remove(len - 1))
232    }
233
234    /// Removes the strings in `range`
235    /// ([`igraph_strvector_remove_section`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_remove_section)).
236    ///
237    /// # Panics
238    /// If the range goes past the end.
239    pub fn remove_section(&mut self, range: std::ops::Range<usize>) {
240        let len = self.len();
241        assert!(
242            range.start <= range.end && range.end <= len,
243            "section {range:?} out of bounds (len {len})"
244        );
245        unsafe {
246            igraph_strvector_remove_section(
247                self,
248                range.start as igraph_int_t,
249                range.end as igraph_int_t,
250            )
251        };
252    }
253
254    /// Appends copies of all the strings of `other`
255    /// ([`igraph_strvector_append`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_append)).
256    pub fn extend_from(&mut self, other: &Self) {
257        crate::error::check(unsafe { igraph_strvector_append(self, other) })
258            .expect("igraph failed to grow a string vector");
259    }
260
261    /// Moves all the strings of `other` to the end of `self`, leaving
262    /// `other` empty, without copying them, like [`Vec::append`]
263    /// ([`igraph_strvector_merge`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_merge)).
264    pub fn append(&mut self, other: &mut Self) {
265        crate::error::check(unsafe { igraph_strvector_merge(self, other) })
266            .expect("igraph failed to grow a string vector");
267    }
268
269    /// Resizes to `len` strings: extra strings are dropped, new ones are
270    /// empty ([`igraph_strvector_resize`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_resize)).
271    pub fn resize(&mut self, len: usize) {
272        crate::error::check(unsafe { igraph_strvector_resize(self, crate::error::int_size(len)) })
273            .expect("igraph failed to resize a string vector");
274    }
275
276    /// Keeps only the first `len` strings (no-op if already shorter).
277    pub fn truncate(&mut self, len: usize) {
278        if len < self.len() {
279            self.resize(len);
280        }
281    }
282
283    /// Removes all strings
284    /// ([`igraph_strvector_clear`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_clear)).
285    pub fn clear(&mut self) {
286        unsafe { igraph_strvector_clear(self) }
287    }
288
289    /// A new vector with the strings at the given positions (repetitions
290    /// allowed) (`igraph_strvector_index`, undocumented in `igraph_strvector.h`).
291    ///
292    /// # Errors
293    /// [`ErrorKind::InvalidValue`](crate::error::ErrorKind::InvalidValue) if
294    /// some position is out of bounds.
295    pub fn select(&self, index: &[igraph_int_t]) -> crate::error::Result<Self> {
296        let len = self.len();
297        if let Some(i) = index.iter().find(|&&i| i < 0 || i as usize >= len) {
298            return Err(crate::error::Error::invalid(format!(
299                "string index {i} out of bounds (len {len})"
300            )));
301        }
302        let idx = crate::vector::VectorInt::view(index);
303        let mut res = Self::new();
304        crate::igraph_call!(igraph_strvector_index(self, &mut res, idx.as_ptr()))?;
305        Ok(res)
306    }
307
308    /// Swaps the strings at `i` and `j`
309    /// ([`igraph_strvector_swap_elements`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_swap_elements)).
310    ///
311    /// # Panics
312    /// If an index is out of bounds.
313    pub fn swap(&mut self, i: usize, j: usize) {
314        let len = self.len();
315        assert!(
316            i < len && j < len,
317            "string indices ({i}, {j}) out of bounds (len {len})"
318        );
319        unsafe { igraph_strvector_swap_elements(self, i as igraph_int_t, j as igraph_int_t) }
320    }
321
322    /// Number of strings that fit without reallocating
323    /// ([`igraph_strvector_capacity`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_capacity)).
324    pub fn capacity(&self) -> usize {
325        unsafe { igraph_strvector_capacity(self) as usize }
326    }
327
328    /// Reserves room for at least `capacity` strings in total
329    /// ([`igraph_strvector_reserve`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_reserve)).
330    pub fn reserve(&mut self, capacity: usize) {
331        crate::error::check(unsafe {
332            igraph_strvector_reserve(self, crate::error::int_size(capacity))
333        })
334        .expect("igraph failed to reserve string vector storage");
335    }
336
337    /// Frees the unused storage
338    /// ([`igraph_strvector_resize_min`](https://igraph.org/c/html/latest/igraph-Data-structures.html#igraph_strvector_resize_min)).
339    pub fn shrink_to_fit(&mut self) {
340        unsafe { igraph_strvector_resize_min(self) }
341    }
342
343    /// Whether some string equals `value`.
344    pub fn contains(&self, value: &str) -> bool {
345        self.position(value).is_some()
346    }
347
348    /// Position of the first string equal to `value`, if any.
349    pub fn position(&self, value: &str) -> Option<usize> {
350        (0..self.len()).find(|&i| {
351            self.get_cstr(i)
352                .is_some_and(|s| s.to_bytes() == value.as_bytes())
353        })
354    }
355}
356
357impl std::ops::Index<usize> for igraph_strvector_t {
358    type Output = str;
359    /// The string at `index`.
360    ///
361    /// # Panics
362    /// If `index` is out of bounds or the string is not valid UTF-8.
363    fn index(&self, index: usize) -> &str {
364        let len = self.len();
365        assert!(
366            index < len,
367            "string index {index} out of bounds (len {len})"
368        );
369        self.get(index).expect("the string is not valid UTF-8")
370    }
371}
372
373impl<S: AsRef<str>> Extend<S> for igraph_strvector_t {
374    fn extend<I: IntoIterator<Item = S>>(&mut self, iter: I) {
375        for s in iter {
376            self.push(s.as_ref());
377        }
378    }
379}
380
381impl From<Vec<String>> for igraph_strvector_t {
382    fn from(items: Vec<String>) -> Self {
383        items.iter().collect()
384    }
385}
386
387impl From<igraph_strvector_t> for Vec<String> {
388    fn from(sv: igraph_strvector_t) -> Self {
389        sv.to_vec()
390    }
391}
392
393impl<'a> IntoIterator for &'a igraph_strvector_t {
394    type Item = String;
395    type IntoIter = Box<dyn Iterator<Item = String> + 'a>;
396    fn into_iter(self) -> Self::IntoIter {
397        Box::new(self.iter())
398    }
399}
400
401impl PartialEq<[&str]> for igraph_strvector_t {
402    fn eq(&self, other: &[&str]) -> bool {
403        self.len() == other.len()
404            && other.iter().enumerate().all(|(i, s)| {
405                self.get_cstr(i)
406                    .is_some_and(|c| c.to_bytes() == s.as_bytes())
407            })
408    }
409}