Correctly specifying the return type for an iterator with a closure

Hello,
I am trying to create a simple helper function for this, but cannot get the type of return value right. Currently I am doing this, and it performs as expected :

//  "Splits" the str into substrs, for each character
for c in "abc".split_inclusive(|c| true) {
    println!("{}", l);
}

Now I would like to convert that to a simple « helper » function. After struggling a little, I got this far (this currently requires nightly for SplitInclusive) :

use std::str::SplitInclusive;

fn chars_str (s: &str) -> SplitInclusive<FnMut(char)->bool> {
    s.split_inclusive(|c| true)
}

fn main() {
    for c in chars_str("abc") {
        println!("{}", c);
    }
}

(Playground with this code.)

…but the compiler complains, telling me that the size of my closure is not known at compile time ?

error[E0277]: the size for values of type `(dyn FnMut(char) -> bool + 'static)` cannot be known at compilation time
    --> src/main.rs:3:27
     |
3    | fn chars_str (s: &str) -> SplitInclusive<FnMut(char)->bool> {
     |                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
     |
     = help: the trait `Sized` is not implemented for `(dyn FnMut(char) -> bool + 'static)`

Now I suppose I could just collect the stuff into a vector in that simplified case, but I would like my helper function to still return an iterator, and I’d like to understand how to specify this kind of return type correctly.
Thank you in advance.

The difficulty here is that the type of a closure cannot be named. You can stick it behind a trait object like dyn FnMut, but trait objects are unsized, so you would have to put that behind a pointer or in a Box.

Instead, a typical approach is to return an opaque type of your own:

// I return a single, concrete type, but that type cannot be named
// (But it can be inferred from the function body -- including inferred
// from types including closures, which can also not be named.)
fn chars_str (s: &str) -> impl Iterator<Item = &str> {
    s.split_inclusive(|c| true)
}

This also gives you the flexibility to change your implementation to use a different iterator in the future.

Edit: Here's a playground with the solution I've suggested plus a few other ways that you can return such an iterator. Feel free to ask for more clarification.

2 Likes

Thank you for your help, it works.
I actually used the second boxed variant you provided, as in this case it allows me to « extend » the str type itself, which « feels » prettier (and it seems currently impossible to do that with impl Iterator) :

trait StrExt {
    fn chars_str(&self) -> Box<dyn Iterator<Item = &str> + '_>;
}
impl StrExt for str {
    fn chars_str (&self) -> Box<dyn Iterator<Item = &str> + '_> {
        Box::new(self.split_inclusive(|c| true))
    }
}

Using trait objects is generally regarded as being less-pretty. Instead, if you wanted to use an extension method, it might be plausible to use an opaque function pointer in lieu of trying to specify the closure type. This incurs less runtime performance drop, since you no longer have to jump through dynamic dispatch:

use core::str::SplitInclusive;

trait StrExt {
    fn chars_str(&self) -> SplitInclusive<fn(char) -> bool>;
}
impl StrExt for str {
    fn chars_str(&self) -> SplitInclusive<fn(char) -> bool> {
        self.split_inclusive((|_| true) as fn(char) -> bool)
    }
}

Note how I said fn(char) -> bool, this is a traditional function pointer (a literal pointer to the start of a function), however this is opaque to the compiler thereafter (in most cases) in contrast to the type of the closure (See this thread, for example).

Using function pointers is still dynamic dispatch.

Thank you.
In that case, I guess I can « hide » the SplitInclusive type behind some custom type of mine, like this ? (So that if its implementation changes later the public API will not. But maybe just creating an alias is not enough, as it still exposes the underlying type… ?)
By the way, can the explicit cast as fn(char) -> bool be removed ? The compiler seems to be OK without it, but maybe it is still better style to keep it ?

use core::str::SplitInclusive;
//  Custom type to hide the internals.
type CharsStrIterator<'a> = SplitInclusive<'a, fn(char) -> bool>;

pub trait StrExt {
    fn chars_str(&self) -> CharsStrIterator;
}
impl StrExt for str {
    fn chars_str (&self) -> CharsStrIterator {
        self.split_inclusive(|c| true)
    }
}

Update : OK this seems to work while hiding the underlying type completely (though it makes a bunch of boilerplate code so I don’t know if it is worth it, or the « best » way to do it, but since I went so far, here it is in case someone wants it) :

use core::str::SplitInclusive;
pub struct CharsStrIterator <'a> {
    data: SplitInclusive<'a, fn(char) -> bool>,
}

impl <'a> Iterator for CharsStrIterator <'a> {
    type Item = &'a str;

    fn next(&mut self) -> Option<&'a str> {
        self.data.next()
    }
}

pub trait StrExt {
    fn chars_str(&self) -> CharsStrIterator;
}
impl StrExt for str {
    fn chars_str (&self) -> CharsStrIterator {
        CharsStrIterator { data: self.split_inclusive(|c| true) }
    }
}

If you really want, you can also nest the impl:

fn chars_str(s: &str) -> SplitInclusive<impl FnMut(char) -> bool> {
    s.split_inclusive(|_| true)
}

You could, but I don't see any reason to do so, unless you intended on directly extending it. Note that a type alias doesn't do anything semantically.

If it can be removed, then that's great :grinning_face_with_smiling_eyes:. I just assumed the compiler wouldn't do such (it might not do so in certain other cases, so I just went with the most conservative option).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.