A little question about using Cow

Hello, everyone. I'm have a little question about using Cow in my project.

Assuming that I have a function which takes a &str, and pushes the parsed result into a Vec:

struct Foo<'a> {
   bar: &'a str,
   baz: &'a str,
}

fn parse_foo<'a>(input: &'a str, results: &mut Vec<Foo<'a>>) {
   // parsing input
}

Anythings works fine. Then I tried to implement Foo with Cow<str> so that we can create a new Foo from both &str and String:

struct Foo<'a> {
   bar: Cow<'a, str>,
   baz: Cow<'a, str>,
}

fn parse_foo_from_string<'a>(input: String, results: &mut Vec<Foo<'a>>) {
    parse_foo(&input, results);
}

Apparently, the second function won't compile due to the lifetime issue. So I create a new function:

fn parse_foo_owned<'a>(input: &str, results: &mut Vec<Foo<'a>>) {
    // ...
}

This function parse_foo_owned does the exactly same thing like parse_foo, except pushing the Owned version of Foo instead of Borrowed.

And now, problem resolved. But I end up having two function which doing the almost same thing which is redundant. Is there any better way?

It's not a lifetime issue. The compiler has saved you from a use-after-free crash. This code

fn parse_foo_from_string<'a>(input: String, results: &mut Vec<Foo<'a>>) {
    parse_foo(&input, results);
}

is exactly like:

fn parse_foo_from_string<'a>(input: String, results: &mut Vec<Foo<'a>>) {
    let tmp = input.as_str();
    parse_foo(tmp, results);
    drop(tmp);
    drop(input);
    // from this point any use of results causes crash
}

You have to change parse_foo<'a>(input: &'a str to take ownership of the input, e.g. parse_foo<'a>(input: Cow<'a, str>, so that it can store owned Cow, and call it with parse_foo(input.into()

2 Likes

Thinks for your replying!.

Firstly, I agreed. The parse_foo_from_string function will definitely cause use after free error. Rust is able to prevent such problems by introducing lifetime. The parse_foo requires the input to live as long as the Foo struct while input is getting dropped right before parse_foo_from_string finishes. So the compiler goes crazy. This is what I mean, "lifetime issue".

Then, taking a Cow<'a, str> instead of &str actually doesn't change nothing. You still have to check whether the input is owned or not:

fn parse<'a>(input: Cow<'a, str>, results: &mut Vec<Cow<'a, str>>) {
    match input {
        Cow::Borrowed(s) => {
            for bar in s.split(' ').filter(|s| !s.is_empty()) {
                results.push(Cow::Borrowed(bar))
            }
        }
        Cow::Owned(s) => {
            for bar in s.split(' ').filter(|s| !s.is_empty()) {
                results.push(Cow::Owned(bar.into()))
            }       
        }
    }
}

The only thing different is that we now have a long function instead of two short functions.

1 Like

You can factor out the parsing:

use std::borrow::Cow;

fn parse(input: &str) -> impl Iterator<Item=&str> {
    input.split(' ').filter(|s| !s.is_empty())
}

fn collect<'a>(input: Cow<'a, str>, results: &mut Vec<Cow<'a, str>>) {
    match input {
        Cow::Borrowed(s) => {
            results.extend(parse(s).map(Cow::Borrowed))
        }
        Cow::Owned(s) => {
            results.extend(parse(&s).map(|s| Cow::Owned(s.to_string())))       
        }
    }
}

If parsing with iterators is too tricky, you can return Vec<&str> from parse and convert it similarly.

1 Like