Questions on Rust Book (Chapters 12.5 and 13.1)

Hi

In Code Listing 12-22, the snippet shows the following

    let results = if config.case_sensitive {
        search(&config.query, &contents)
    } else {
        search_case_insensitive(&config.query, &contents)
    };

If I put semicolons at the end of the statements for the function calls "search" or "search_case_insensitive", I will get an error about if and else having incompatible types and a message suggesting to remove the semicolon.

Then in Code Listing 13-5

    let expensive_closure = |num| {
        println!("calculating slowly...");
        thread::sleep(Duration::from_secs(2));
        num
    };

Semicolons are allowed for the "println!" and "sleep" calls.

My main question is both listings are using let statements, so why is it that putting a semicolon for the example in Code Listing 12-22 will throw an error but it doesn't for Code Listing 13-5? I am sure I misunderstood/missed some concepts but not sure what they are.

A block in Rust can take a value, and this value is the last expression found the block if that expression has no semi-colon. If the last expression has a semi-colon, the block takes on the value ().

Here's an example:

fn make_string(i: i32) -> String {
    
    let a_string = if i > 0 {
        let foo = "abc".to_string();
        foo + "def"
    } else {
        "hello world".to_string()
    };
    
    let b_string = {
        std::thread::sleep(Duration::from_secs(1));
        "bar"
    };
    
    a_string + b_string
}

playground

In this case there are four blocks in the program. Let's look at them one at a time.

The first block in the if is the following:

{
    let foo = "abc".to_string();
    foo + "def"
}

Executing this block would first create foo, and then the value of the block becomes "abcdef". There is also another block in the if:

{
    "hello world".to_string()
}

This block just takes the value "hello world". The value of the full if expression, and hence the value assigned to a_string is the value of the block that the if chooses.

There is also this block:

{
    std::thread::sleep(Duration::from_secs(1));
    "bar"
}

Computing the value of this block involves first waiting a second, and then the value is "bar". Thus b_string will be assigned the value "bar" after one second.

Finally there's the full block of the function:

{
    let a_string = if i > 0 {
        let foo = "abc".to_string();
        foo + "def"
    } else {
        "hello world".to_string()
    };
    
    let b_string = {
        std::thread::sleep(Duration::from_secs(1));
        "bar"
    };
    
    a_string + b_string
}

This block takes the value a_string + b_string, and this value is what the function will return.

In your examples, the println! and thread::sleep calls have a semi-colon because they are not the value assigned to the block they are in. The num variable does not have a semi-colon because it is the value of the block.

3 Likes

Hi Alice

Thanks for the detailed explanation. I have another question regarding this. For example, I could modify Code Listing 13-5 to the following and Rust compiler allows it (I changed the last line to "return ....;")

	let expensive_closure = |num|
	{
		println!("calculating slowly");
		thread::sleep(Duration::from_secs(2));

		return num;
	};

However, it will cause an error if I were to do the following for Code Listing 12-22

    let results = if config.case_sensitive {
        return (search(&config.query, &contents));
    } else {
        search_case_insensitive(&config.query, &contents)
    };

There will be an error "unexpected token". I suppose I am writing the code wrongly?

This snippet on itself doesn't trigger tokenizer error. Maybe it's the other part(just above this snippet?) which triggers it.

Note that return doesn't do the same as giving a block a value by leaving off the semicolon. The return keyword always returns from the function, so unless you're in the outermost block, the current block is never given a value. A return jumps out of the function regardless of where it is called.

In your case, you are using return in a closure, and closures are functions, so the return is actually in the outermost block of the closure, which means that in this particular case, it does the same as just typing num, but it wouldn't in most cases, including your if.

Hi Alice

Thanks for the explanation.

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.