"Sometimes owned" strings


#1

And another one…

I’m passing structured messages between various parts of a server, which come from strings sent via a network protocol. E.g.

enum Msg<'a> {
    Msgtype1 { key: &'a str, val: &'a str },
    Msgtype2 { key: &'a str },
    // etc.
}

The Msg implementation then knows how to convert to string. Now the fields here I made &str because most of the time, they point into strings stored either in a message from the client (while it is being processed) or in a hashmap in the server. I would like to stringify them as late as possible, however sometimes the member have to be created fresh, and so I can’t put them into a Msg and pass them around since they don’t live long enough.

What would be the right strategy here? I suppose there isn’t a string that behaves like a &str but “knows” that it actually owns its data, so that it has to free it when getting destroyed…


#2

It sounds like you want a string that is either owned or borrowed. If so, then you could use std::borrow::Cow<str>. It should be almost a drop in replacement because of auto deref.


#3

Thanks, that looks like exactly what I wanted!

I suppose there is no way around the .into_cow() calls when initializing my structure?


#4

IntoCow is unstable, and I’m not sure there is a use for it given the presence of convert::Into.

If you know you have a borrowed string, then use Cow::Borrowed(borrowed_str). If you know you have an owned string, then use Cow::Owned(owned_str). If you want to accept either without requiring callers to convert to Cow explicitly, then you can use, e.g., fn foo<'a, I: Into<Cow<'a, str>>>(s: I), then s.into() should give you the right Cow value while enabling the caller to pass either a borrowed or an owned string.

Without more context on the problem you’re trying to solve, I’m not sure how to best answer your question. Why is calling into() a problem? You need a construct a Cow<'a, str> value somehow.


#5

It’s not a problem, it’s just a bit less nice to read all the struct initializations. But .into() works and is a bit shorter.


#6

You can make your setter methods generic over T: Into<Cow<str>> and call .into() inside methods:

fn new<T: Into<Cow<str>>>(s: T) -> Struct {
  Struct(s.into())
}

// later
Struct::new("value");
Struct::new(String::new());

#7

That won’t really help; I was thinking about the new (or in my case parse) method, which must construct the struct instances itself - and since there are a lot of different enum tags, there is a lot of into_cow()ing. But at least I can shorten it to into()! Looks nice enough.