Is it possible to concat string and make it be a string view?


#1

Can I have a string view which consists of few different strings? I’d like to create a &str from &str but with additional strings. Sounds weird without an example: lets say I have a my string string. I want to create another string from this one: \"my string\" so this is just about putting quotes on both sides. Making this a separate object is really wasteful - allocating more space, copies and so on - why not to simply create a view which had 3 pointers to data chunks: \", my string and \"? This \" could be in the data section though. Is it even possible, at least in theory?


#2

I’m assuming “my string” is not a literal/constant? If so, a &str is a contiguous memory region. You can perhaps create an iterator that produces the concatenation, but it won’t be a slice.


#3

Rather than a &str, I think your best bet is

enum StrTree<'a> {
    Str(&'a str),
    Tree(Vec<StrTree<'a>>),
}

impl<'a> fmt::Display for StrTree<'a> { ... }

#4

Then, I’d need to implement some String trait for that iterator for working with this structure as I’d have a simple slice. This would be really useful I guess. What do you think?


#5

Looks interesting. I think it worths to try implementing this.


#6

I think it depends on what you plan to do with the concatenated string. Mostly, I wanted to point out that you cannot form a slice out of 3 arbitrary memory regions (each holding a slice).


#7

I think because of different lifetimes, is not it?


#8

Even if you had 3 &'static str slices, you cannot pretend they’re just 1 (this is the key part) contiguous slice.


#9

You actually can pretend implementing something like str trait for this new type. But of course the memory behind it is not a contiguous memory chunk obviously.


#10

You can pretend, but it’ll fail in fun ways :slight_smile:. Honestly, if you’re contemplating something like @ExpHP’s StrTree, just allocate a String and use that as the concat buffer. If these are transient concats, then you can pool/reuse these Strings if you’re worried about allocations.


#11

&str is by definition a pointer and a length. There’s no way to fit 3 pointers and 3 lengths in struct {pointer, length}, so there’s no way to have such str


#12

BTW, another thing you can consider, if your strings are small, is to use the smallvec crate. You can then allocate a SmallVec<[u8; <some_size>]> on the stack, push the u8s to it, and then call str::from_utf8_unchecked (or the checked version, if you want) to form a slice.


#13

What about this?

use std::fmt;

struct StrView<'a> {
    parts: Vec<&'a str>,
}
impl<'a> StrView<'a> {
    fn new() -> StrView<'a> {
        StrView {
            parts: Vec::default(),
        }
    }
}
impl<'a> fmt::Display for StrView<'a> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for i in &self.parts {
            fmt.write_str(i)?;
        }
        Ok(())
    }
}

fn main() {
    let third = "efg";
    let fourth = format!("blad, world: {}", third);
    let mut str_view = StrView::new();
    str_view.parts.push("abc");
    str_view.parts.push("cde");
    str_view.parts.push(third);
    str_view.parts.push(&fourth);

    println!("strview: {}", str_view);
}

It would be nice to decouple &str interface to a StrLike trait and implement it for &str and for such type so we can work with such string views.


#14

It’s a bit hard to suggest/comment on StrView - what are you trying to optimize for? You mentioned minimizing allocations in the initial post, but this has allocations. I also wouldn’t call this a string “view” - it’s just a collection of string slices. A &str is a view - it’s a very lightweight window into some existing storage. This is similar to C++17’s std::string_view.

If your goal is to just Display a bunch of string slices with minimal allocations, you can probably just do something like:

struct DisplaySlices<'a, I: 'a> {
    iter: &'a I,
}

impl<'a, I, A> fmt::Display for DisplaySlices<'a, I>
where
    &'a I: IntoIterator<Item = A>,
    A: AsRef<str>,
{
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        for i in self.iter {
            fmt.write_str(i.as_ref())?;
        }
        Ok(())
    }
}

let d = DisplaySlices {
        iter: &["abc", "cde", third, &fourth],
    };
println!("DisplaySlices: {}", d);

If your goal (or what you’re optimizing for) is something else, then please elaborate :slight_smile:.


#15

I might be missing something, but if you’re trying to do exactly your example from the first post you can just take a slice from the middle of the full str, like this:

fn main() {
    let quoted = "\"hello, world\"";
    let unquoted = &quoted[1..quoted.len()-1];
    
    println!("Quoted: {}", quoted);
    println!("Unquoted: {}", unquoted);
}

#16

Yes, I did not reach anything with this Vec because I still allocate memory on the heap for storing pointers there. The thing I would like to implement is creating such a structure which could allocate memory on the stack, have StrLike-interface and combined several &str objects inside so we may have something similar to string_view objects in C++17 but for multiple strings. I guess I am too optimistic with that, am I?


#17

Thanks for your participating in the topic. However, no, I would like to do the opposite thing - quote a string.


#18

If you want a &str interface, then you’d need to emplace/copy the &strs into a buffer. As mentioned, smallvec might be suitable.

I’m still unclear on what operations you’re going to be doing with these strings. If it’s just display, then there are good solutions for that with no allocation, as shown.