Luke Cycon
Luke Cycon

Reputation: 648

Replacing part of &str in Rust Vec

I have written the following code:

use std::str::{from_utf8};

struct JSONPointer {
    segments: Vec<String>
}

fn build_json_pointer(s: Vec<String>) -> JSONPointer {
    JSONPointer { segments: s.iter().map(|x| x.replace("~1", "/").replace("~0", "~")).collect() }
}

fn main() {
    let v = vec!["foo".to_string(), "bar".to_string(), "baz~1".to_string()];
    let p = build_json_pointer(v);

    println!("Hello world! {:?}", p.segments);
}

This all works well with Strings, but ideally my JSONPointer struct would contain a Vec<&str> (if only for learning purposes). The issue I run into takes place in the call to map the replaces a few reserved strings. No matter the combination of conversions I use, I am always told that my borrowed value does not live long enough.

I understand that I am taking a reference to a local value and trying to return it (leading to a possible use-after-delete error), but I can't seem to find a way to take a copy of the string.

EDIT: Updated to minimum reproducible form

I realize now that when I was referring to wanting a "copy" (heap allocated), that's exactly what String is. There was talk about a cleaner way of doing this possibly though.

Upvotes: 0

Views: 1990

Answers (1)

Shepmaster
Shepmaster

Reputation: 430634

If you are worried about needlessly allocating strings, there is the possibility of using a Cow:

use std::borrow::Cow;

struct JSONPointer<'a> {
    segments: Vec<Cow<'a, str>>,
}

fn replace_if<'a>(s: Cow<'a, str>, from: &str, to: &str) -> Cow<'a, str> {
    if s.contains(from) {
        Cow::Owned(s.replace(from, to))
    } else {
        s
    }
}

fn build_json_pointer<'a>(s: &[&'a str]) -> JSONPointer<'a> {
    let segments = s
        .iter()
        .copied()
        .map(Cow::Borrowed)
        .map(|x| replace_if(x, "~1", "/"))
        .map(|x| replace_if(x, "~0", "~"))
        .collect();

    JSONPointer { segments }
}

fn main() {
    let v = vec!["foo", "bar", "baz~1"];
    let p = build_json_pointer(&v);

    println!("Hello world! {:?}", p.segments);
}

This has the advantage that it doesn't need to allocate any memory when no replacements need to be made, but has the downside that each pattern has be be searched twice. It could potentially be even more efficient with a structure like a rope.

For your original case that accepted a String, you could do something similar without Cow by keeping the original String and not unconditionally replacing it:

struct JSONPointer {
    segments: Vec<String>,
}

fn replace_if(s: String, from: &str, to: &str) -> String {
    if s.contains(from) {
        s.replace(from, to)
    } else {
        s
    }
}

fn build_json_pointer(s: &[String]) -> JSONPointer {
    let segments = s
        .iter()
        .cloned()
        .map(|x| replace_if(x, "~1", "/"))
        .map(|x| replace_if(x, "~0", "~"))
        .collect();

    JSONPointer { segments }
}

fn main() {
    let v = vec!["foo".to_string(), "bar".to_string(), "baz~1".to_string()];
    let p = build_json_pointer(&v);

    println!("Hello world! {:?}", p.segments);
}

Upvotes: 4

Related Questions