03 Rustlings if Solution

if, is the most basic but still very versatile type of control flow, these next couple of exercises will go through this little word.

Additional Reading

if1.rs

// if1.rs
// Execute `rustlings hint if1` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub fn bigger(a: i32, b: i32) -> i32 {
    // Complete this function to return the bigger number!
    // Do not use:
    // - another function call
    // - additional variables
}

// Don't mind this for now :)
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn ten_is_bigger_than_eight() {
        assert_eq!(10, bigger(10, 8));
    }

    #[test]
    fn fortytwo_is_bigger_than_thirtytwo() {
        assert_eq!(42, bigger(32, 42));
    }
}

Alright looks like we have our first real writing of some basic code -- we need to create an if statement that will compare 2 numbers or variables a or b and return which one is bigger.

if1.rs errors

⚠️  Compiling of exercises/if/if1.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
 --> exercises/if/if1.rs:6:34
  |
6 | pub fn bigger(a: i32, b: i32) -> i32 {
  |        ------                    ^^^ expected `i32`, found `()`
  |        |
  |        implicitly returns `()` as its body has no tail or `return` expression
  |
note: consider returning one of these bindings
 --> exercises/if/if1.rs:6:15
  |
6 | pub fn bigger(a: i32, b: i32) -> i32 {
  |               ^       ^

error: aborting due to previous error

Let's look at what the rust_errors are telling us here.

The line ^^^ expectedi32, found ()``is essentially telling us that there is no return expression because there is nothing in the body of the functionbigger.

The solution is pretty simple, it's to add an if a < b expression.

if1.rs Solution

pub fn bigger(a: i32, b: i32) -> i32 {
    if a < b {
        b
    } else {
        a
    }
}

This compiles but the hint reminds me that it can also be done in one line, let's try that, it works just fine and looks like this

pub fn bigger(a: i32, b:i32) -> i32 {
	if a < b { b } else { a }
}

I'm also reminded that :

  • in Rust the if condition doesn't need to be surrounded by parentheses
  • if/else conditionals are expressions
  • Each condition is followed by a {} block

Okay, let's move on to the next exercise.

if2.rs

// if2.rs

// Step 1: Make me compile!
// Step 2: Get the bar_for_fuzz and default_to_baz tests passing!
// Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub fn foo_if_fizz(fizzish: &str) -> &str {
    if fizzish == "fizz" {
        "foo"
    } else {
        1
    }
}

// No test changes needed!
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn foo_for_fizz() {
        assert_eq!(foo_if_fizz("fizz"), "foo")
    }

    #[test]
    fn bar_for_fuzz() {
        assert_eq!(foo_if_fizz("fuzz"), "bar")
    }

    #[test]
    fn default_to_baz() {
        assert_eq!(foo_if_fizz("literally anything"), "baz")
    }
}

if2.rs errors

⚠️  Compiling of exercises/if/if2.rs failed! Please try again. Here's the output:
error[E0308]: mismatched types
  --> exercises/if/if2.rs:13:9
   |
9  | pub fn foo_if_fizz(fizzish: &str) -> &str {
   |                                      ---- expected `&str` because of return type
...
13 |         1
   |         ^ expected `&str`, found integer

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

The first comments state that we first have to make the code compile, so the first thing to do in this case is to add the bar by replacing the 1 which the Rust compile clearly states that it expected an &str and this is an integer.

// if2.rs

// Step 1: Make me compile!
// Step 2: Get the bar_for_fuzz and default_to_baz tests passing!
// Execute `rustlings hint if2` or use the `hint` watch subcommand for a hint.

// I AM NOT DONE

pub fn foo_if_fizz(fizzish: &str) -> &str {
    if fizzish == "fizz" {
        "foo"
    } else {
        "bar" // add bar to get it to compile
    }
}

// No test changes needed!
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn foo_for_fizz() {
        assert_eq!(foo_if_fizz("fizz"), "foo")
    }

    #[test]
    fn bar_for_fuzz() {
        assert_eq!(foo_if_fizz("fuzz"), "bar")
    }

    #[test]
    fn default_to_baz() {
        assert_eq!(foo_if_fizz("literally anything"), "baz")
    }
}

Once we replace this with bar it compiles, but it still doesn't pass the test, which is our step 2. in the comments.

⚠️  Testing of exercises/if/if2.rs failed! Please try again. Here's the output:

running 3 tests
test tests::bar_for_fuzz ... ok
test tests::foo_for_fizz ... ok
test tests::default_to_baz ... FAILED

successes:

successes:
    tests::bar_for_fuzz
    tests::foo_for_fizz

failures:

---- tests::default_to_baz stdout ----
thread 'tests::default_to_baz' panicked at 'assertion failed: `(left == right)`
  left: `"bar"`,
 right: `"baz"`', exercises/if/if2.rs:34:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::default_to_baz

test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

So here we see that the first two test are passing bar_for_fuzz and foo_for_fizz but default_to_baz is not. So let's take a closer look at the tests and see why this is happening.

foo_for_fizz test

    #[test]
    fn foo_for_fizz() {
        assert_eq!(foo_if_fizz("fizz"), "foo") // first test clearly defined in our `if` statement
    }

which is in our code with

if fizzish == "fizz" {
	"foo"
}

bar_for_fuzz test

    #[test]
    fn bar_for_fuzz() {
        assert_eq!(foo_if_fizz("fuzz"), "bar") // if we have "fuzz" we should get "bar"
    }

This tells us that if have Fuzz then we get bar, but this is working only because we have "bar" as our else not because we have "fuzz" defined.

We do so by adding an else if statement like this:

else if fizzish == "fuzz" {
	"bar"
}

This will meet the conditions of our second test

default_to_baz test

#[test]
    fn default_to_baz() {
        assert_eq!(foo_if_fizz("literally anything"), "baz")
    }

In our final test we see that it's saying whatever else, doesn't matter what it is, just give me "baz" so we can easily do that by adding our final else condition to our foo_if_fizz function, that would look like this:

pub fn foo_if_fizz(fizzish: &str) -> &str {
    if fizzish == "fizz" {
        "foo"
    } else if fizzish == "fuzz" {
        "bar"
    } else { // whatever else it is, just make it `"baz"`
        "baz"
    }
}

This gets our code to compile and pass it's tests successfully.

Progress: [########>---------------------------------------------------] 14/94
✅ Successfully tested exercises/if/if2.rs!

🎉 🎉  The code is compiling, and the tests pass! 🎉 🎉

Alright, I guess we get our first quiz after this 😧.

Conclusion

In this blog post, we delved into Rust if expressions, which are a fundamental part of the language's control flow. We explored how to use if, else if, and else statements to create conditionals and solve problems in Rust. Additionally, we learned about Rust's unique features, such as not requiring parentheses around if conditions and the treatment of if/else conditionals as expressions. By understanding and mastering Rust if expressions, you'll be better equipped to write efficient and clean Rust code for various applications.