Wrapper for items in iterator over FFI slice generated by bindgen

I am trying to create a clean Rust API over FFI bindings generated by bindgen. The complete code below is available on the Playground.

A design goal is to use the FFI structs for storing data and avoid allocation additional memory and copying, maintaining duplicate structures, etc.

This is a simplified facsimile of the FFI generated by bindgen:

struct ffi_item { /* elided for brevity */}
struct ffi_bucket {
    items: [ffi_item], /* simplified */
}
impl ffi_bucket {
    /// orignal ffi method returns a slice from a ptr and the number of 
    /// items. Lifetimes are implicit and the same as in this method.
    fn items(&self) -> &[ffi_item] {
        &self.items
    }
}

The iterator is what is giving me trouble:

impl<'s> MyBucket<'s> {
    fn items(&self) -> ItemIterator<'s> {
        ItemIterator {
            items: unsafe { &*self.bucket }.items(),
            item: None,
            index: 0,
        }   
    }
}

While the code compiles, I cannot get the implementation of the Iterator right:

struct ItemIterator<'s> {
    items: &'s [ffi_item],
    item: Option<MyItem<'s>>,
    index: usize,
}
impl<'s> Iterator for ItemIterator<'s> {
    type Item = &'s MyItem<'s>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.items.len() {
            self.item = Some(MyItem { item: &self.items[self.index] });
            self.index += 1;
            self.item.as_ref()
        } else {
            None
        }
    }
}

It fails with the following error:

error: lifetime may not live long enough
  --> src/lib.rs:44:13
   |
37 | impl<'s> Iterator for ItemIterator<'s> {
   |      -- lifetime `'s` defined here
...
40 |     fn next(&mut self) -> Option<Self::Item> {
   |             - let's call the lifetime of this reference `'1`
...
44 |             self.item.as_ref()
   |             ^^^^^^^^^^^^^^^^^^ method was supposed to return data with lifetime `'s` but it is returning data with lifetime `'1`

Given that I want to provide a safe and simplified wrapper API around the FFI generated by bindgen, how do I best implement an iterator over the ffi_item in the ffi_bucket?

The Iterator trait doesn't support "lending iterators", where the returned items are reborrows of *self in the next method. For example, if you have a field MyItem<'s>, you can't return a &MyItem<'s> to that field, as that's a borrow of the field that reborrows *self.

In the playground, this is a sufficient change:

 impl<'s> Iterator for ItemIterator<'s> {
-    type Item = &'s MyItem<'s>;
+    type Item = MyItem<'s>;

     fn next(&mut self) -> Option<Self::Item> {
         if self.index < self.items.len() {
             self.item = Some(MyItem { item: &self.items[self.index] });
             self.index += 1;
-            self.item.as_ref()
+            self.item.as_ref().map(|m| { MyItem { item: m.item }})
         } else {
             None
         }
     }
 }

With this change, the code effectively returns a copy of self.item.

Alternatively you could make MyItem implement Copy and just return a copy of self.item directly.

1 Like

Thanks for the answer!

As I was creating an instance of MyItem<'s> for the sole reason of returning a reference in next(&mut self) I might as well return it directly:

    fn next(&mut self) -> Option<Self::Item> {
        if self.index < self.items.len() {
            let item = MyItem { item: &self.items[self.index - 1] };
            self.index += 1;
            Some(item)
        } else {
            None
        }
    }

... and the idea of returning references to MyItem<'s> is problematic. To use references I really need to maintain an entire collection of MyItems<'s>. I did that in a previous iteration, now I remember why.

The whole point of iterating over MyItem<'s> references in MyBucket<'s> though, is that there is also a native Rust implementation that does not wrap ffi_item.

Here I am maintaining a collection of MyItem<'s> as there are no ffi_item and would like to avoid copy or remapping to new MyItem<'s>instances.

Need to think a bit more, thoughts welcome!

1 Like