Cannot compile the function

Hi everyone !

Currently I am working on my zip function, which should return iterator and I want it to accept both &str and String without extra allocations, so it should use Cow under the hood. The function:

pub fn zip<'a, S>(
    labels: &'a [S],
    values: &'a [S],
) -> impl Iterator<Item=impl Iterator<Item=(S, S)> + 'a> 
    where 
        S: Into<Cow<'a, str>> 
{
    values
        .chunks(labels.len())
        .map(move |chunk| {
            chunk
                .iter()
                .zip(labels.iter())
                .map(|(&l, &v)| (l.into(), v.into()))
        })
}

But I got an error when try to compile the code:

error[E0271]: expected `{closure@main.rs:16:22}` to be a closure that returns `(S, S)`, but it returns `(Cow<'_, str>, Cow<'_, str>)`
 --> src/main.rs:6:25
  |
3 | pub fn zip<'a, S>(
  |                - expected this type parameter
...
6 | ) -> impl Iterator<Item=impl Iterator<Item=(S, S)> + 'a> 
  |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `(S, S)`, found `(Cow<'_, str>, Cow<'_, str>)`
  |
  = note: expected tuple `(S, S)`
             found tuple `(Cow<'_, str>, Cow<'_, str>)`
  = note: required for `Map<Zip<Iter<'_, S>, Iter<'_, S>>, {closure@main.rs:16:22}>` to implement `Iterator`

Sandbox: Rust Playground

Am I using the generics wrong in such way ?

Could somebody explain the error and help me to fix the function ?

P.S. why I need this function. I have a Modal (I implementing it for ratatui-rs). And I want it to be universal for any input (input will be in the form Vec<Vec<(&str, &str)>>), and I want to avoid extra allocations such as using vectors, or using .clone()/.to_string()/.to_owned() etc.

The iterator which zip function return I want to use to build Vec<List>, where List is a component from ratatui-rs.

Example of input:

I receive characters: Vec<Object>, where each Object { guid: u64, name: String }
I want my Modal to draw list of items in the form "Name: <CharName> -- Guid: <CharGuid>"

You don't want to take ownership (go from s: &String to Cow::Owned(s.clone())), you want to end up with &strs. AsRef<str> is a more appropriate tool for the job.

pub fn zip<'a, S>(
    labels: &'a [S],
    values: &'a [S],
) -> impl Iterator<Item=impl Iterator<Item=(&'a str, &'a str)>> 
    where 
        S: AsRef<str>
{
// ...
                .map(|(l, v)| (l.as_ref(), v.as_ref()))
// ...
}

(I made it a bit more general in the playground.)

It was suggesting you might have meant this,[1] but that's just getting you closer to the end of a dead end IMO.


  1. note the change in the return type ↩︎

2 Likes

Thank you very much !

Could you tell why you have used to_owned in your example ?

let labels = ["name".to_owned(), "value".to_owned(), "test".to_owned()];

is it a typo ?

the code works also without it. Btw, what is the actual difference between to_string and to_owned in context of the &str ? both created new String.

That was just to demonstrate that you could pass in a slice of String and a slice of &str and it still works (if you have the two generic type parameters instead of one).

The former notionally goes through the formatting machinery whereas the second is more direct. The resulting String is the same though.

Using to_string instead of to_owned used to be considered a performance footgun, but then some implementations were specialized so it was less of one, and the constant advice to use to_owned instead of to_string faded away on forums such as this. Then it turned out that specialization wasn't going to be stabilized any time soon,[1] and now some people want to get rid of that particular specialization in std. So at least personally I'm back to using to_owned even though it's less self-documenting IMO.[2]


  1. std gets to utilize unstable features, which is why the specialization is possible in std ↩︎

  2. Some people have the opposite opinion, but I don't remember why. ↩︎

2 Likes