Borrowed str does not live long enough in match

I have the following code:

        let code = match self.0.code {
            KeyCode::Esc => "esc",
            KeyCode::Enter => "enter",
            KeyCode::Left => "left",
            KeyCode::Right => "right",
            KeyCode::Up => "up",
            KeyCode::Down => "down",
            KeyCode::Home => "home",
            KeyCode::End => "end",
            KeyCode::PageUp => "pageup",
            KeyCode::PageDown => "pagedown",
            KeyCode::BackTab => "backtab",
            KeyCode::Backspace => "backspace",
            KeyCode::Delete => "delete",
            KeyCode::Insert => "insert",
            KeyCode::F(1) => "f1",
            KeyCode::F(2) => "f2",
            KeyCode::F(3) => "f3",
            KeyCode::F(4) => "f4",
            KeyCode::F(5) => "f5",
            KeyCode::F(6) => "f6",
            KeyCode::F(7) => "f7",
            KeyCode::F(8) => "f8",
            KeyCode::F(9) => "f9",
            KeyCode::F(10) => "f10",
            KeyCode::F(11) => "f11",
            KeyCode::F(12) => "f12",
            KeyCode::Tab => "tab",
            KeyCode::Char(' ') => "space",
            KeyCode::Char(c) => {
                let char_str = c.to_string();

Returning char_str.as_str() is obviously problematic, since we are trying to return a borrowed value, even though the lifetime of char_str ends at the end of that scope. I see two ways of solving this: Returning a String instead, or returning a Cow::Owned. I find the String solution a little unnecessary, so I want to try to use Cow which I have never used before in Rust. Can someone explain how a solution with Cow is better than with String here? What are the benefits of using Cow? Is the Cow solution going to be more efficient (maybe use less memory) if the Cow::Borrowed cases are selected at runtime, and only have the String overhead when the Cow::Owned case is used? Or is there some other, even easier, solution to my problem? Thanks!
One thing I don't like about the Cow solution is that I would have to wrap all the string literals from all the other match arms in a Cow::Borrowed. Is there some simpler syntax to do this maybe?

Cow is probably the most semantically correct return type here – sometimes, you are returning a borrowed string, sometimes an owned one.

No, it will use (very slightly) more memory, because it will need one byte for storing the discriminant of the enum. This doesn't matter almost at all, though, it's a trivial "cost". What matters is that now you have to match on the enum to extract the inner value, which is a branch at runtime. Whether that might matter for you is up to your use case.

Early-return with Cow::Owned() if the key code is Char, then wrap the entire remaining match into Cow::Borrowed.


I'm a little confused which one you claim uses "slightly more memory"? Cow or String? If you mean Cow uses slightly more, then can you explain what the advantage is of using Cow in this case over String?

yes, Cow<str> will be more efficient as for memory usage: when the Cow::Borrowed is active, it only stores a reference, just as &str.

not really, you must do the conversion explicitly, there's no auto coersion here. you can write "str".into(), which is shorter than Cow::Borrowed("str"), but you can't get rid of the explicit conversion.

this is a more important question to ask, and the answer, as always, depends on how exactly you will use the converted result: there might be better solutions for certain specific use cases, please provide more context.

but just for the lifetime problem in your code snippet, Cow<str> should be a good first attempt. but one thing I'm not clear was, why do yo need this conversion in the first place? why not use the KeyCode type directly?

1 Like

The reason for the conversion is to implement fmt::Display on a custom-defined struct that wraps KeyEvent (containing KeyCode). Sadly KeyCode (from an external library) doesn't provide fmt::Display itself.

In that case you can just write!(f, "{}", char) feed everything directly into the fmt::Formatter. No need to go through a &str.

That would work, but I am also parsing the modifier seperately and adding that on conditionally. I think the Cow solution works best for now.

Calling write_str/write_char directly on the formatter will be better than making a variable.

In terms of memory use Cow vs String it depends on how many time Borrowed/Owned get used.

size_of::<Cow<str>> == max(
) + size_of::<discriminant>

so Cow is slightly bigger than just a String. The advantage is that it doesn't always allocate.

1 Like

When talking about size of values, it is important to recognize that there are two vastly different notions of size: shallow size and deep size. Shallow size is the amount of bytes that's stored directly on the struct, whereas deep size consider all data owned by the struct, including those behind references. Deep size is generally not known at compile time, and is not clearly defined in the case of shared ownership.

As explained above, Cow will always have larger shallow size than its inner type. Therefore, Cow's real advantage is in the case it refers to a shared data it doesn't own. In some cases, Cow can have far smaller deep size by virtue of not cloning data.

However, references are usually 8 bytes long and your string is less than a dozen bytes long. In such cases, you're not saving much by not cloning. From a pure performance point-of-view, you might have better luck with one of the "small string" crates.

From a pure performance point-of-view that is. Any code that process keyboard inputs are unlikely to be performance-critical, so in practice code clarity wins out. You should write whatever is easier to read and maintain, and that's probably not going to involve Cow or external crates. I'd say returning String is quite reasonable choice here.


This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.