What is "impl Iterator" return type, and how might I pass it over FFI to be used from Swift

For example I have this struct that returns an iterator:

pub struct NumberGenerator {
}

impl NumberGenerator {
  pub fn numbers(&self) -> impl Iterator<Item = usize> {
    ...
  }
}

And then I expose NumberGenerator through FFI as described on The Rust FFI Omnibus:

#[no_mangle]
pub unsafe extern "C" fn number_generator_new() -> *mut NumberGenerator {
  Box::into_raw(Box::new(NumberGenerator::new()))
}

#[no_mangle]
pub unsafe extern "C" fn number_generator_free(ptr: *mut NumberGenerator) {
  if ptr.is_null() { return }
  Box::from_raw(ptr);
}

But I’m having trouble figuring how to expose the numbers Iterator return type (impl Iterator<Item = usize>). If it was a concrete type (say NumberGeneratorIterator) then I think I could do it using the same object pattern that I’m using for the NumberGenerator structure above.

But I guess I don’t really understand what impl Iterator<Item = usize> is/means and how I would expose it through FFI. Is there a way to return this iterator without having to define my own NumberGeneratorIterator type?

Thanks,
Jesse

What impl Trait means is "some specific but anonymous type that implements the mentioned Trait".
So the numbers fn promises to return some unnamed type that behaves like an Iterator over usize elements.

As the actual type is by design hidden, if you want to cross the FFI boundary it’s probably best to box it, and treat it like an opaque pointer.

2 Likes

The impl Iterator<Item = usize> actually is a concrete type, but it’s been ‘anonymized’ so that you won’t know the exact type that is returned. Presumably because it is either unnameable (a closure type), entirely unwieldy and large, or because the writer of the function simply doesn’t want you to have to know what it is.

Wait, then what’s the difference between impl trait and dyn trait?

impl trait is a placeholder for a concrete type that will be determined at compile time, whereas dyn trait is an interface that uses dynamic dispatch at runtime.

Particularly for functions like Iterator::next() that get called a lot in inner loops, knowing the type at compile time often allows it to be inlined and optimized in a way that dynamic dispatch can’t. This is one of the big advantages of impl trait.

2 Likes

And I presume impl trait is Sized, opposite to how dyn trait is not?

Not necessarily, I don’t think, unless the trait you’re working with also requires Sized, or if you specify that you’re returning impl MyTrait + Sized.

1 Like

Using impl Trait should behave exactly the same as if the actual type was written in its place, in both argument and return position. So I think the same restrictions apply.

1 Like

Regarding sending iterators over an FFI - you certainly can box it up and expose the enclosing struct, along with some helper methods for getting the next element, etc. However, if you’re going to be calling this iterator a lot, you’ll be incurring the cost of a function call each time, which can’t be inlined across an FFI boundary. Rust’s iterator APIs are designed specifically to encourage the compiler to inline them, so you’d potentially be giving up a lot of performance.

Depending on how it’s being used, you might prefer to collect all the elements into an array or vector, and just pass that across the FFI boundary. Whether you allocate the memory on the Rust side or the Swift side is up to you - either way, you have to be mindful of ownership. But you avoid a lot of FFI calls that way.

7 Likes

Thanks for your help.

I’m still a bit lost on how exactly to accomplish the “treat it like an opaque pointer” part. I’m relatively new to rust and haven’t done much programming where I need to think about pointer details.

I think I might have the first part working. I box the numbers iterator and then cast to a c_void. That seems to compile and be callable from Swift.

#[no_mangle]
pub unsafe extern "C" fn number_generator_numbers(ptr: *mut NumberGenerator) -> *mut c_void {
  assert!(!ptr.is_null());
  let number_generator = &mut *ptr;
  let numbers = number_generator.numbers();
  let boxed = Box::into_raw(Box::new(numbers));
  boxed as *mut c_void
}

But when I try to implement a function to call next I don’t know how to get the boxed number iterator back out of the c_void. I’m also not sure how best to handle None case… right now I return max usize as a marker that I check for, but maybe there’s a better approach? Here’s what I have so far:

#[no_mangle]
pub unsafe extern "C" fn number_generator_iterator_next(ptr: *mut c_void) -> usize {
  let numbers_iterator = ???;
  numbers_iterator.next().unwrap_or(std::usize::MAX)
}

Thanks,
Jesse

Thanks for the tip, the iterator approach seemed cleaner for what I was trying to do, but I I’ll test with plain arrays once I get the iterator approach working.

impl MyTrait is implicitly Sized. You can use impl MyTrait + ?Sized to opt out of that.

For example, AsRef doesn’t require Sized, but the compiler still want impl AsRef to be Sized:

// error[E0277]: the size for values of type `str` cannot be known at compilation time
fn a(s: &'static str) -> &impl AsRef<str> {
    s
}

// compiles:
fn a(s: &'static str) -> &(impl AsRef<str> + ?Sized) {
    s
}
1 Like

Did you look at the http://jakegoulding.com/rust-ffi-omnibus/objects/ page specifically? You can follow a similar pattern - return a pointer to foreign code, and take the pointer from foreign code (you don’t necessarily need to use c_void there). The “opaque pointer” treatment is from the foreign code perspective (Swift, in this case) - it has a pointer that it receives from the Rust code, but this pointer isn’t directly used by Swift. Instead, it passes it back to the Rust FFI code, where the FFI function then does something, but on the Rust side. It’s like a token, if you will.

As for the iterator, easiest will be to not use impl Iterator - create a concrete named type, and expose that.

I’ll second other suggestions to not cross the FFI boundary for each next() - this is very chatty/granular, and performance will be fairly bad (unless next() takes a long time itself to produce a value). It also requires a wider FFI surface, which makes your job harder. Try to come up with a higher level interface, one where Swift asks Rust code to do something more bulky in one go.

1 Like

Oh, and as for this part, you can define a #[repr(C)] struct consisting of the usize value and a bool indicating whether the usize is valid or not. Swift code will then check that bool before trying to read the usize.

1 Like

Yes, I'm following that pattern for most of the objects that I expose through FFI.

But in the case if iterators I have a few different cases in my rust API where I return -> impl Iterator... ... I was hoping to keep that pattern so that I don't have to expose the underlying iterator type, or create a new wrapping type for each case.

If I do want my underlying rust api to return -> impl Iterator... I just discovered that I can wrap that in my ffi implementation like this and pass it back and forth between rust and C:

pub struct WrappingIteratorForFFI {
  inner: Box<dyn Iterator<Item = usize>>
}

Of course that's a bunch of extra indirection... is there a more direct way, or is that best I can do?

Anyway thanks for your help. I think I'll probably use the above and if it's too slow then I'll just implement concrete types for each iterator as you suggest above.

My goal with using iterators is so that I can stream results into a list view. The underlying iterator is being created by a channel receiver's into_iter() method. My goal is to start getting results shown in the list view before the rust side has finished generating the full set of all results on separate threads.

Ohh, I guess I can do it this way too for a little more direct:

pub struct WrappingIteratorForFFI {
  inner: Box<Iterator<Item = usize>>
}

This is the same thing as what you had in the previous post :slight_smile:.

If you have a bunch of different iterator impls but want to carry them around in a single “container”, then a trait object (like you have) is a fine way to go.

Little different... I took out the dyn part.

By same thing I meant you have the same trait object - the dyn keyword is just the modern syntax to express that you have a dynamic object inside, and is the syntax you should prefer. It’s otherwise exactly the same.

3 Likes

Please note that in the example above, if you can know the actual iterator type (rather than impl Iterator<Item=...>) you can remove the Box. That also means getting rid of the heap allocation that Box performs, which in turn makes WrappingIteratorForFFI a newtype (which means it’s essentially become a free-at-runtime wrapper around the actual Iterator type).

2 Likes