Search code examples
rustreturnmatchexpression

How/when to use expressions instead of returns in rust


I'm doing this advent of code in rust to learn it (started today with the rust book too since the language is becoming more interesting to me) and I'm having some doubts as of how to comply with rust style.

Just read in the book that in rust is more idiomatic to use an expression at the end of a function than a return statement, so I've been going through these past days challenges and refactoring them for this but I have some doubts.

First the commits where I change it from returns to expressions:

  1. https://github.com/nerock/AdventOfCode2022/commit/db9649760b18b92bf56de6586791285522caf2b4
  2. https://github.com/nerock/AdventOfCode2022/commit/b98b68c0fa8c7df0dcdba14eb642400468781084

If you look at day1.rs method get_top_three, I've modified it where I create a variable, and I assign it in an if, else if, else but my initial idea was to not have the else at all and have something like

if current > first {
    (current, first, second);
} else if current > second {
    top_three = (first, current, second);
} else if current > third {
    top_three = (first, second, current);
}

(first, second, third)

would this be possible in some way and maybe better? I've gotten used to avoid having an else expression and just returning the "default" result but maybe this is not the way in rust.

Besides this I'm still not sure when to use match in place of if, so if any of you look at my code and has some comments about my uses (or anything else to be honest) it would be greatly appreciated.


Solution

  • With a lot of missing context and guessing, I assume that your question is as follows.

    You had the code:

    fn get_top_three(current: i32, first: i32, second: i32, third: i32) -> (i32, i32, i32) {
        if current > first {
            return (current, first, second);
        } else if current > second {
            return (first, current, second);
        } else if current > third {
            return (first, second, current);
        }
    
        (first, second, third)
    }
    

    And you heard that it's better to not do return, so you refactored it to:

    fn get_top_three(current: i32, first: i32, second: i32, third: i32) -> (i32, i32, i32) {
        let top_three: (i32, i32, i32);
    
        if current > first {
            top_three = (current, first, second);
        } else if current > second {
            top_three = (first, current, second);
        } else if current > third {
            top_three = (first, second, current);
        } else {
            top_three = (first, second, third)
        }
    
        top_three
    }
    

    And now your question is, is this better? If yes, why? What could you do differently?


    The answer is: Yes and no. What you are missing is that everything can be used as a return expression. Including, and very important in this case, if statements.

    So you can rewrite the entire function like this:

    fn get_top_three(current: i32, first: i32, second: i32, third: i32) -> (i32, i32, i32) {
        if current > first {
            (current, first, second)
        } else if current > second {
            (first, current, second)
        } else if current > third {
            (first, second, current)
        } else {
            (first, second, third)
        }
    }
    

    And maybe with this in mind, you now realize how powerful those expressions really are :)

    So why does this work?

    Everything in Rust has a value. For example:

    • Every block of code in a {} has a value:
      fn main() {
          let x = {
              let y = 10;
              y
          };
          println!("{}", x);
      }
      
      10
      
    • Every if-else has a value:
      fn main() {
          let condition = false;
          let y = if condition { 5 } else { 15 };
          println!("{}", y);
      }
      
      15
      

    And so on. Basically every block of code that is surrounded by {} has a value, and you can set its value by a value expression in its last line. Just like at the end of a function. And you can assign its value directly to a variable, or forward it to its surrounding code block (like the way I refactored your code, where the value of the if block gets directly forwarded to the function).

    That is what makes this language feature so powerful.