Iterator, mutable reference and FFI

Hi everybody,

I'm using FFI (generated using bindgen) to interface with an external C library. This library has a struct to implement an iterator (c_iter) and a routine (c_next()) to access the next item, another struct c_item.

I'm wrapping this unsafe code in a RUST iterator, something like:

impl Iterator for c_iter {
    type Item = *mut c_item;

    fn next(&mut self) -> Option<Self::Item> {
        let it = unsafe { c_next(self) };

        if it.is_null() {
            None
        } else {
            Some(it)
        }
    }
}

This works correctly but I really do not like returning a *mut c_item since my code is forced to use unsafe again and as_mut() to get a mutable reference to c_item.

At the same time I cannot use as_mut() in the next() code because I cannot have a lifetime on the Item. For example this is clearly not allowed:

impl<'a> Iterator for c_iter {
    type Item = &'a mut c_item;

    fn next(&mut self) -> Option<Self::Item> {
        unsafe { c_next(self).as_mut() }
    }
}

I'm a bit confused on what are my options here.

What is the lifetime of the item? Do you get a owned value that you have to call free() on, or does it get invalidated when you call c_next()?

Can you use PhantomData to let you give the iterator a lifetime for whatever data you're iterating over?

So maybe like this:

/// The type being iterated over.
struct MyNativeList {
    ...
}

impl MyNativeList {
    pub fn iter(&self) -> impl Iterator<Item = Item<'_>> + '_ {
        NativeListIter { ... }
    }
}

/// The iterator's internal state.
struct NativeListIter<'a> {
    iter: *mut c_iter,
    _lifetime: PhantomData<&'a ()>,
}

/// A wrapper around a single item.
struct Item<'a> {
    inner: *mut c_item,
    _lifetime: PhantomData<&'a ()>,
}

impl<'a> Iterator for NativeListIter<'a> {
    type Item = Item<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let ptr = get_next_item(self.iter);

        if ptr.is_null() {
            None
        } else {
            Some(Item { inner: ptr, _lifetime: PhantomData })
        }
    }
}

Something to keep in mind is I'm using an Item type instead of returning a c_item directly. You'll want to do this anyway if you're writing FFI bindings, seeing as returning a struct that can enforce encapsulation and lifetimes is more idiomatic than returning a raw pointer.

It does get invalidate every time I call c_next(). I also have another version (i.e. c_next_no_free()) that I can use and in that case I have to call free() on the item but I'm not using it currently.

If it gets invalidated, you have to use (streaming-iterator)[https://github.com/sfackler/streaming-iterator].
Iterator items are independend can have to remain valid (so you can call .collect() and put all of them in a Vec for example).
So you would have to use c_next_no_free here and free it in the Items drop impl. That would probably solve your problem, I think?

1 Like
struct Item {
    inner: *mut c_item
}
impl Deref for Item {
    type Target = c_item;
    fn deref(&self) -> &c_item {
        unsafe { &*self.inner }
    }
}
impl Drop for Item {
    fn drop(&mut self) {
        unsafe {
            c_free(self.item);
        }
    }
}

@s3bk @Michael-F-Bryan I put together your suggestions and came up with something like this. What do you think?

pub struct GpiodChipIter {
    inner: *mut gpiod_chip_iter,
}

impl Drop for GpiodChipIter {
    fn drop(&mut self) {
        unsafe { gpiod_chip_iter_free_noclose(self.inner) };
    }
}

pub struct GpiodChip {
    inner: *mut gpiod_chip,
}

impl Drop for GpiodChip {
    fn drop(&mut self) {
        unsafe { gpiod_chip_close(self.inner) };
    }
}

pub fn make_gpiod_chip_iter() -> Result<GpiodChipIter, &'static str> {
    let inner = unsafe { gpiod_chip_iter_new() };

    if inner.is_null() {
        Err("error creating GPIO chip iterator")
    } else {
        Ok(GpiodChipIter { inner })
    }
}

impl Iterator for GpiodChipIter {
    type Item = GpiodChip;

    fn next(&mut self) -> Option<Self::Item> {
        let inner = unsafe { gpiod_chip_iter_next_noclose(self.inner) };

        if inner.is_null() {
            None
        } else {
            Some(GpiodChip { inner })
        }
    }
}

impl GpiodChip {
    pub fn name(&self) -> Result<&str, Utf8Error> {
        unsafe { CStr::from_ptr(gpiod_chip_name(self.inner)).to_str() }
    }

    pub fn label(&self) -> Result<&str, Utf8Error> {
        unsafe { CStr::from_ptr(gpiod_chip_label(self.inner)).to_str() }
    }

    pub fn num_lines(&self) -> u32 {
        unsafe { gpiod_chip_num_lines(self.inner) }
    }
}

I don't see any obvious mistakes.
However, I do have one question: does *mut gpiod_chip_iter require that *mut gpiod_chip is alive?

If so, I would suggest adding _m: PhantomData<&'a GpiodChip> to GpiodChipIter.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.