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:
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?