Beginner's fail to return new struct with &str-members

Hi all,

I'm struggling with &str, struct and borrow. I expected fn get_property below to put all chars from parameter line into struct Property's members name and value. I ask myself: The str "Hallo" / "Hallo:World" is there and owned by fn main, so how can it disappear when fn get_property sets up a struct?

As I got Cow-doc any Cow relates to data that does not have to be owned as long as it is present.

What am I missing? Pls don't be captain obvious and reply: all :wink:

Chris

use std::borrow::Cow;

struct Property<'b> {
    name: Cow<'b, str>,
    value: Cow<'b, str>,
}

fn get_property<'c>(line: &'c str) -> Option<Property<'c>> {
    let mut buffer = String::with_capacity(line.len());
    let mut name = String::with_capacity(line.len());
    let mut value = String::with_capacity(line.len());
    for c in line.chars() {
        buffer.push(c);
        if c == ':' {
            name.push_str(buffer.as_str());
            name.pop().unwrap();
            buffer.clear();
        }
        println!("{}\t{}", c, buffer);
    }
    if name.is_empty() || value.is_empty() {
        None
    }
    else {
        // late push for further elaboration
        value.push_str(buffer.as_str());
        Some(Property{ name: name.into(), value: value.into() })
    }
}

fn main() {
    assert!(get_property("Hallo").is_none());
    assert!(get_property("Hallo:World").is_some());
    let p = get_property("Hallo:World").unwrap();
    assert_eq!(p.name, "Hallo");
    assert_eq!(p.value, "World");
}

(Playground)

Output:

H	H
a	Ha
l	Hal
l	Hall
o	Hallo
H	H
a	Ha
l	Hal
l	Hall
o	Hallo
:	
W	W
o	Wo
r	Wor
l	Worl
d	World

Errors:

   Compiling playground v0.0.1 (/playground)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.53s
     Running `target/debug/playground`
thread 'main' panicked at src/main.rs:33:5:
assertion failed: get_property("Hallo:World").is_some()
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

There's a number of improvements that could be made, but as to why the assert is failing: you never modify value before this check, so you always return None.

    if name.is_empty() || value.is_empty() {
        None
    }

The problem is that get_property() returns None if name.is_empty() || value.is_empty() — but value is always empty at that point, as it only has any content added to it when the condition is false. Presumably, you want to change the condition to just if name.is_empty().

As for other improvements, what do you want to do in this case?

"one:two:three"

Currently you end up with a name of onetwo and a value three; I'm not sure that's intentional.

Based on what you want to do, consider using some of the methods on str instead of manually iterating over characters:

You're currently always returning the owned variant of Cow, but you may be able to return the borrowed variant instead, depending on the particulars.

5 Likes

You are never using the borrow variant of Cow. Yoiu could do it like this:

fn get_property<'a>(line: &'a str) -> Option<Property<'a>> {
    let (name, value) = line.split_once(':')?;
    if value.len() == 0 || name.len() == 0 {
        return None
    }
    Some(Property {
        name: name.into(),
        value: value.into()
    })
}
1 Like