Match pattern &str and ignoring case

I am working on matching commands from my Redis wannabe. I have the following:

let line = self.parse_stream(&buffer, len);
      println!("{:?}", line);
      match line {
        line => {
          match line[2] {
            "ECHO" => {
              self.write_frame(
                &Frame::Bulk(line[4].to_string())).await?;
            }
            "echo" => {
...

I am having to duplicate match code because I can't figure out how to change "ECHO" to lower case. On the other hand, am I saving any stack memory by keeping the string slice or should I just convert all to String or &String?

To avoid duplication in what you've already written, use an or-pattern:

            "ECHO" | "echo" => {
              self.write_frame(
                &Frame::Bulk(line[4].to_string())).await?;
            }

If you want to handle mixed case, that will require 16 literals or performing a case conversion. If it's okay to mutate buffer, then you can use str::make_ascii_uppercase to do that in-place without further allocation.

4 Likes

There is also str::eq_ignore_ascii_case

            l if l.eq_ignore_ascii_case("echo") => {
              self.write_frame(
                &Frame::Bulk(line[4].to_string())).await?;
            }

Of course, since likely multiple strings are being matched against, one can avoid the need to do re-do the case-conversion upon each comparison, and also make the match more readable, by using make_ascii_lowercase or …uppercase nonetheless, and matching against "echo" or "ECHO" in the match, respectively.

5 Likes

The or pattern is interesting. I thought I tried that and got an error. Been working too hard. I think that's the best way to go. Trying str::make_ascii_uppercase created problems since I parsed a buffer and put the results in a Vec<&String>. Then other parts of the code would error with Got String expected &str.

You are doing something else wrong, then, because a &String transparently Deref-coerces to a &str.

Here's where I parse the buffer. It's not original but lifted from the fine folks at Tokio and probably altered. If I try to use make_ascii_uppercase here, I get an error that I can't borrow the local variable &line[start..i] as mutable. Don't know if that has something to do with the lifetime.

  fn parse_stream<'a>(&'a self, line: &'a [u8], l: usize) -> Vec<&str> {
    let mut lines  = vec![];
    let mut start = 0;
    let end = l - 1;
    for i in start..end {
      if line[i] == b'\r' && line[i + 1] == b'\n' {
        lines.push(std::str::from_utf8(&line[start..i]).unwrap());
        start = i + 2;
      }
    }
    return lines
  }

This code does not reproduce the error.

Btw the use of an explicit lifetime on 'self is almost always a mistake or not what you intended. You likely wanted something like

fn parse_stream<'a>(&self, line: &'a [u8]) -> Vec<&'a str>

Ok, fixed that. I assume that self doesn't need the lifetime because self does hang around for the life of the program and removing that requires the life time is put on the return type to make sure it lasts after the return. But where do I use str::make_ascii_uppercase?

For that, you'd change the line type to &mut [u8] and apply it right before str::from_utf8(). That'd require splitting the borrows. An easier solution might be to just apply it before you do anything else with the string.

The do-it-upfront approach is trivial; the split-the-borrow approach is demonstrated here.

I was answering just before I saw you edited your answer. Yep, still having issues.

 lines.push(str::make_ascii_lowercase(std::str::from_utf8(&line[start..i]).unwrap()));
     |                    ------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ in mutability
     |                    |
     |                    arguments to this function are incorrect
     |

Thanks.  I'll look the demo



Actually, the easiest appears to be to go with what I have in the match portion so that this

      match line {
        line => {
          match line[2] {
            "ECHO" | "echo" => {
              self.write_frame(
                &Frame::Bulk(line[4].to_string())).await?;
            },

            "PING" | "ping" => {
              self.write_frame(
                &Frame::Simple("PONG".to_string())).await?;
            }

Doing otherwise the only thing I can make work is to return Vec from stream_parse, then I convert my match terms as "echo".to_string() but I am under the impression that String takes up more memory and I could be very wrong. Thank you for the all the help. I forgot to ask. Was I correct that a lifetime on self is just silly. Inside the Impl the lifetime the length for the program unless explicitly "dropped"?

Don't use str::make_ascii_lowercase. I intentionally used the slice version, not the str version. The str version requires a &mut str which is really hard to get hold of.

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.