[SOLVED] Issue with using Cow<'a, str> and accessing str methods (ex: to_uppercase)


#1

I am trying to write a function that can get slice/vector of strings (String or &str). So after some looking around and reading Joe Wilm’s post. I decided to use Cow<'a, str>!

I started with a function that just accepts reference to a Vec, thinking that I can do slices later I wrote something similar to this snippet. But I’ve run into issue that can be simplified in following snippet (Playground link) that will give #E0619:

use std::borrow::Cow;

fn main() {
    let v = vec!["ab", "cd"];
    upper(&v);
}

fn upper<'a, S>(q: &Vec<S>)
where
    S: Into<Cow<'a, str>>,
    S: std::fmt::Debug,
{
    let mut v: Vec<String> = Vec::new();
    for word in q {
        v.push(word.into().to_uppercase());
    }
    println!("{:?}", v);
}

My understanding is that since word has Into<Cow<'a, str>> bound, calling .into() will result a Cow and according to documentation since Cow implements Deref, I can then call to_uppercase()

I tried to fix the error by separating .into and to_uppercase which didn’t help:

let c: Cow<'a, str> = word.into();
v.push(c.to_uppercase());

How can this be achieved? or is this something not possible with Cows?

Thanks in advance


#2

The problem is that S is bound to be Into<Cow<...>> but word is &S. If you bound &'a S: Into<Cow<'a, str>> then we run afoul of str being unsized which violates Vec requirement. It’s a rabbit hole from there and I couldn’t find a good way to make it work, which is a bit irksome given the conceptual simplicity of what is being attempted.

Having said that, based on what you wrote in your post, Cow is the wrong type anyway - you want S: AsRef<str> and that works perfectly well.


#3

+1 for AsRef. The bound S: AsRef<str> means you can take a shared reference to S and view it as a &str. No lifetimes necessary, which is always a good sign.

fn upper<S>(q: &[S])
where
    S: AsRef<str> + Debug
{
    let mut v = Vec::new();
    for word in q {
        v.push(word.as_ref().to_uppercase());
    }
    println!("{:?}", v);
}

A similar bound is used in Command::args to set up arguments of a subprocess. That one is even more general because it takes any kind of iterable data, not just vec or slice.


#4

Heads up @jwilm about the confusion from the blog post.


#5

Thanks. I got into that rabbit hole very quickly too :slight_smile: and shared the same feeling of “simple thing” not easily possible.
I guess since I was used to code like this where references were taken care of for me:

fn foo(vref: &Vec<&str>) {
    vref.iter().filter(|item| item.contains("bar"));
}

I expected that word.into() will figure that S.into() should be called not (&S).into()

Thanks again.


#6

Thanks for the Commnad link, it make my intended API much better.


#7

Trying to follow that example, however I don’t want to use into_iter (as is done in for loop), how do I specify q's bound such that I can call .iter() on it rather than into_iter()


#8

You can’t - iter() is not part of any trait.

What’s the reason you don’t want to use into_iter?


#9

Note that into_iter() on a Vec<T> gives you an iterator over items of type T, but into_iter() on a &Vec<T> gives you an iterator over items of type &T so I think it just does what you want here.


#10

I am trying to search another collection using content of q, and I get cannot move out of captured outer variable in an FnMut closure error:

fn search<I, S>(q: I)
where
    I: IntoIterator<Item = S>,
    S: AsRef<str>,
{
    let other_collection = vec!["a", "b"];
    other_collection.into_iter().filter(|item| {
        q.into_iter()
            .all(|word| some_predicate(item, word.as_ref()))
    });
}

Playground


#11

You need to adjust it a bit for that use case:

fn search<'a, I: 'a, S>(q: &'a I)
where
    &'a I: IntoIterator<Item = S>,
    S: AsRef<str>,
{
    let other_collection = vec!["a", "b"];
    other_collection.into_iter().filter(|item| {
        q.into_iter().all(|word| some_predicate(item, word.as_ref()))
    });
}

This will not consume q on each inner iteration since it’s a reference.


#12

Thanks a lot :+1:

So can I conclude that there is no way to put a bound on an argument to be able to convert it to something that iterates over reference to items (.iter()) rather than taking ownership of them (into_iter())?

Also, trying to understand why your adjustment make it work is hurting my brain :slight_smile:


#13

The issue here is that into_iter() is called multiple times, once for each filter() invocation. into_iter is defined as taking self as an argument. The problem is that this will consume whatever self happens to be. If it’s a value, like Vec<&str>, then you can only call it once. If it’s a reference, like &Vec<&str>, then you can call it multiple times since self is a reference and that doesn’t consume the underlying object. So the issue isn’t so much about what the iterator returns but actually getting a fresh iterator each time.

In theory one should be able to abstract over this with AsRef or Borrow but it doesn’t work here from what I’ve tried.

Abstracting over owned vs borrowed is sometimes possible and easy, but not always.