It's possible to use a single `let` to declare a bunch of variables to be filled later

Rust uses definitive assignment, so sometimes it makes sense to write the code like this:

let x;
...
x = 92
...

Today I realized that this pattern extends nicely for cases where you want to fill a bunch of variables:

        let (s, e, attrs); // <- The trick, can define the whole bunch at once.
        if self.attr_pos == self.text.attrs.len() {
            s = self.pos;
            e = self.text.string.len();
            attrs = Attrs::default();
        } else {
            let (attr_start, attr_end) = self.text.attrs[self.attr_pos].range;
            if attr_start > self.pos {
                s = self.pos;
                e = attr_start;
                attrs = Attrs::default();
            } else {
                s = attr_start;
                e = attr_end;
                attrs = self.text.attrs[self.attr_pos].attrs.clone();
                self.attr_pos += 1;
            }
        }
        self.pos = e;
        Some((&self.text.string[s..e], attrs))
7 Likes

Wait, hold on, this compiles?

let (x, y);
x = 10;
y = 20;

I could have sworn that I tried this and that I could only get it to work with one variable per let statement.

1 Like

Nifty trick.

1 Like

I've personally found the assignment-after-declaration pattern to be useful for getting a temporary value inside a conditional block to live longer, since the lifetime is based on the position of the let statement. For example, I recently wrote something similar to this:

use std::{
    fs::File,
    io::{self, prelude::*},
};

pub enum Example {
    Foo,
    Bar,
    Baz(usize),
}

pub fn write_enum(value: Example) -> io::Result<()> {
    let buf: String;
    let bytes = match value {
        Example::Foo => b"foo\n",
        Example::Bar => b"bar\n",
        Example::Baz(x) => {
            buf = format!("baz {x}\n");
            buf.as_bytes()
        }
    };
    let mut file = File::create("example.txt")?;
    file.write_all(bytes)
}

By declaring buf in the outer block, we can unify the runtime String buffer with the compile-time &'static [u8] literals, so we can use a simple &[u8] instead of a full Cow<[u8]>. (Effectively, we're letting the compiler-generated drop flag act as an ad-hoc enum.)

4 Likes

You might have been trying let x, y; and not let (x, y); - the former is "multiple independent variables", but the latter is essentially the syntax for tuple destructuring, just, well, without the tuple itself.

1 Like

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.