Skip to content

Instantly share code, notes, and snippets.

@mcclure
Last active December 12, 2022 21:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mcclure/b3163c2cd71dbc7c68b5c697652c9a0d to your computer and use it in GitHub Desktop.
Save mcclure/b3163c2cd71dbc7c68b5c697652c9a0d to your computer and use it in GitHub Desktop.

So I find myself writing this match statement:

line.push(match ch {
    'a'..='z' => { (ch as u8) - ('a' as u8) }
    'S' => { start = Some(UVec2::new(line.len() as u32, match grid { None => 0, Some(ref grid) => grid.nrows() as u32 })); 0  }
    'E' => { end   = Some(UVec2::new(line.len() as u32, match grid { None => 0, Some(ref grid) => grid.nrows() as u32 })); 25 }
    _ => return invalid2()
})

The idea is that for a lowercase letter I produce its alphabet value, and for the special characters 'S' and 'E' I also save the current "grid position", where the "grid position" is this kinda hairy expression.

I really don't like this code repetition between 'S' and 'E'. So I do what I would do in any other language with closures, which is I write a helper function for constructing the "grid position":

line.push({
    let at =  Some(UVec2::new(line.len() as u32, match grid { None => 0, Some(ref grid) => grid.nrows() as u32 }));
    match ch {
        'a'..='z' => { (ch as u8) - ('a' as u8) }
        'S' => { start = at(); 0  }
        'E' => { end   = at(); 25 }
        _ => return invalid2()
    }
})

And this works fine! So my problem's solved. But I still feel like I'm missing something about Rust ergonomics/idioms.
Because the problem with this is for all code inside the outer {}— in other words, all match branches— both line and grid are borrowed by the at closure.

Let's imagine, hypothetically, I had to add another special character, 'G', which does... something complicated with line, which requires line to be mutably borrowed by the 'G' match block. Now I can't use my at()anymore becauseatborrowsgrid` and you can't have a mutable borrow, such as the one I will need in this hypothetical 'G' branch, if other borrows exist.
How would a normal Rust programmer solve this situation? I keep running into situations where a closure I wrote purely for my own convenience implicitly captures, borrows, and breaks things.

The only things I can think of are:

  1. instead of capturing grid and/orline in at, pass them in as an argument— which if instead of "at" capturing two variables it captured nine, would get tiresome real fast, or
  2. Do 'S' | 'E', which does work, but then I'm going to have to put a second inner match or if testing on ch right after I've already matched it once, which is in its way also ugly.
  3. A macro?? I guess??

It seems like either I'm missing something about how to write cleanly structured Rust, or people who write a lot of Rust just tolerate a higher level of mild repetition (EG repeatedly calling a function with the same nine parameters or repeatedly indexing into a complex structure with the same four indexes in the same order) than I'm used to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment