user270199
user270199

Reputation: 1417

Construct string slice from vector of string slices

I have a vector holding n string slices. I would like to construct a string slice based on these.

fn main() {
    let v: Vec<&str> = vec!["foo", "bar"];
    let h: &str = "home";

    let result = format!("hello={}@{}&{}@{}", v[0], h, v[1], h);

    println!("{}", result);
}

I searched through the docs but I failed to find anything on this subject.

Upvotes: 2

Views: 1217

Answers (2)

tedtanner
tedtanner

Reputation: 695

You will need to iterate over the vector as cdhowie suggests above. Let me explain why this is necessarily an O(n) problem and you can't create a single string slice from a vector of string slices without iterating over the vector:

Your vector only holds references to the strings; it doesn't hold the strings themselves. The strings are likely not stored contiguously in memory (only their references inside your vector are) so combining them into a single slice is not as simple as creating a slice that points to the beginning of the first string referenced in the vector and then extending the size of the slice.

Given that a &str is just an integer indicating the length of the slice and a pointer to a location in memory or the application binary where a str (essentially an array of char's) is stored, you can imagine that if the first &str in your vector references a string on the stack and the next one references a hardcoded string that is stored in the executable binary of the program, there is no way to create a single &str that points to both str's without copying at least one of them (in practice, probably both of them will be copied).

In order to get a single string slice from all of those &str's in your vector, you need to copy each of the str's they reference to a single, contiguous chunk of memory and then create a slice of that chunk. That copying requires iterating over the vector.

Upvotes: 1

cdhowie
cdhowie

Reputation: 169403

This can be done (somewhat inefficiently) with iterators:

let result = format!("hello={}",
    v.iter().map(|s| format!("{}@{}", s, h))
        .collect::<Vec<_>>()
        .join("&")
);

(Playground)

If high performance is needed, a loop that builds a String will be quite a bit faster. The approach above allocates an additional String for each input &str, then a vector to hold them all before finally joining them together.


Here's a more efficient way to implement this. The operation carried out by this function is to call the passed function for each element in the iterator, giving it access to the std::fmt::Write reference passed in, and sticking the iterator in between successive calls. (Note that String implements std::fmt::Write!)

use std::fmt::Write;

fn delimited_write<W, I, V, F>(writer: &mut W, seq: I, delim: &str, mut func: F)
    -> Result<(), std::fmt::Error>
    where W: Write,
        I: IntoIterator<Item=V>,
        F: FnMut(&mut W, V) -> Result<(), std::fmt::Error>
{
    let mut iter = seq.into_iter();
    
    match iter.next() {
        None => { },
        Some(v) => {
            func(writer, v)?;
    
            for v in iter {
                writer.write_str(delim)?;
                func(writer, v)?;
            }
        },
    };
    
    Ok(())
}

You'd use it to implement your operation like so:

use std::fmt::Write;

fn main() {
    let v: Vec<&str> = vec!["foo", "bar"];
    let h: &str = "home";
    
    let mut result: String = "hello=".to_string();
    
    delimited_write(&mut result, v.iter(), "&", |w, i| {
        write!(w, "{}@{}", i, h)
    }).expect("write succeeded");
    
    println!("{}", result);
}

It's not as pretty, but it makes no temporary String or Vec allocations. (Playground)

Upvotes: 2

Related Questions