Huston we have an indenting problem

So I just finished the rust book (amazing documentation btw thank you Steve Klabnik, Carol Nichols, and Chris Krycho), got through the http server section, and now that I'm looking back at what I wrote the most obvious thing that stands out is that I'm thinking there's really not a great way to reduce the indentation on this.

The basic work being done here is very simple it's just an infinite loop pulling an Option off a queue matching it to get the contents and running 2 lines of code per case, and yet somehow that translates to 6 levels of indentation.

Now, I like my screen space, quite a bit actually, mainly because I tend to have a lot of files open at the same time (for the same reasons that I currently have 17 tabs open: I don't like closing things, what if i need it), so naturally I don't like code thats 90% horizontal whitespace.

With that said, I have two questions:

  1. Is this as bad as it gets or do larger projects tend to get even worse than this?
  2. What's the best way to go about mitigating indenting?
while let Some(job) = job_queue.lock().unwrap().recv() { 
    ...
}
println!("disconnected");
1 Like

This construct does not work well when returned enum is Result or any enum with more variants than Option has (i wish there was some syntax for that, since match statement makes indentation deep). It is not possible to handle error value, for example if we had if let Ok(result) = .. else { .. }.

One thing that could be done to minimize indentation, function event_loop could be defined outside Worker::new, you could save 2 indentation levels.

Often times what i do in similar cases is:

fn event_loop(rx:..) {
    loop {
        match rx.recv() {
            Ok(item) => handle_ok_case(item),
            Err(e) => {
                // handle error case, often by logging error and breaking loop.
            }
        }
    }
}

fn handle_ok_case(..) {
   // do whatever in loop iteration must be done.
}

When some code block is having too many lines or has too deep indentation, it is possible to refactor it in little reusable functions. You can use #[inline] if inline'ing is desirable. The problem though is that it is not always possible to refactor code following this pattern, especially in async code or when references with certain lifetimes are borrowed often times due to borrow checkers limitations.

2 Likes
let no_jobs = |err| {
    println!("Worker {id} disconnected because of {err:?}; shutting down.");
    ()
};
while let Ok(job) = job_queue.lock().unwrap().recv().map_err(no_jobs) {
    ...
}
1 Like

Unfortunately this is one of the things the book was explicit about being a bad idea, mainly because the mutex thats acquired in the job_queue.lock() step isn’t dropped as soon as expression is finished executing. See: From Single-Threaded to Multithreaded Server - The Rust Programming Language

To my eye that does not look over indented. It's simple and clear enough the read and understand quickly and if I'm counting about right it all fits with 80 columns. I feel that refactoring into a little function of functions somehow or whatever would just add complexity with no gain in readability.

5 Likes

I haven’t found it to be a problem. Mostly I end up with 4 to 5 levels of indentation, the largest I could find was 7.

You can put the expression in brackets to force it to drop temporaries:

while let Ok(job) = {job_queue.lock().unwrap().recv()} {
    println!("Worker {id} got a job; executing");
    job();
}

println!("Worker {id} disconnected; shutting down");

Also rust is whitespace insensitive:

impl Worker {
fn new(id: usize, job_queue: Arc<Mutex<Receiver<Job>>>) -> Worker {
    let exec_loop = move || loop {
        let job = job_queue.lock().unwrap().recv();
        match job {
            Ok(job) => {
                println!("Worker {id} got a job; executing");
                job();
            },
            Err(_) => {
                println!("Worker {id} disconnected; shutting down");
                break;
            }
        }
    };

    Worker {
        id,
        thread: thread::spawn(exec_loop)
    }
}
}

Heresy, I know :P

Insane. All programming languages should provide a way to adjust indentation between 2, 4, N spaces/tabs. I try to always use 2 spaces.

The Rust compiler does not care about your indentation, This compiles fine:

struct Worker{}impl Worker{fn new()->Worker{Worker{}}}fn main(){let _w=Worker::new();}

All in all I'm very happy cargo fmt ensures we all use the same layout. Even if I don't totally agree with its details. That is one less thing to have to think about, check for and argue about. You can reconfigure that if you like but that is kind of anti-social.

3 Likes

Haha. Your definition of "anti-social" apparently means not following what all the would-be cool kids are doing, without scrutiny or vetting. I ain't no follower. I am society.

I wish the online consensus were in favor of tolerating minor variations in coding style rather than enforcing uniformity via tooling. When I hear "ending a debate" I think "agreeing to disagree," not "outlawing a particular stance."

Who needs "consensus"? What if you are dealing with a bunch of old fools who happen to be "majority"?

Consensus gave the people VHS though Beta was superior quality.

If Google had stayed on existing "consensus" they wouldn't have created Blink, and just kept using WebKit, and or even more limiting, just used Netscape Navigator themselves.

Still further, there wouldn't be Rust. Folks woulda just kept extending C++. Etc.

Majority opinion is overrated.

The discussion has diverged enough from the original topic and doesn't seem productive anymore. I understand being frustrated, but further venting doesn't make for a good discussion environment

3 Likes