Julia O
Julia O

Reputation: 241

Scope of 'let' with shadowing and String -> &str conversion

With the following code, I tried to return &str of temperature of user input, but in vain. Then, I am trying to return f32, but still struggle...

Q1. The reason I am getting the error at the bottom is because the scope of 'let temp = String::new();' still persists, even I 'shadow' it later by 'let temp = temp.trim().parse::<f32>();' within the loop?

Q2. How can I rewrite the code so that it returns &str?

fn gettemp() -> f32 {
    let temp = String::new();

    loop {
        println!("What is your temperature?");

        io::stdin().read_line(&mut temp).expect("Failed to read the line");

        let temp = temp.trim().parse::<f32>();

        if !temp.is_ok() {
            println!("Not a number!");
        } else {
            break;
        }
    }

    temp
}

Error:

error[E0308]: mismatched types
  --> src/main.rs:70:5
   |
49 | fn gettemp() -> f32 {
   |                 --- expected `f32` because of return type
...
70 |     temp
   |     ^^^^ expected f32, found struct `std::string::String`
   |
   = note: expected type `f32`
              found type `std::string::String`

Upvotes: 0

Views: 151

Answers (3)

zrzka
zrzka

Reputation: 21249

A1 - nope, that's not how shadowing works. Let's look at your code with comments.

fn gettemp() -> f32 {
    let temp = String::new(); // Outer

    loop {
        // There's no inner temp at this point, even in the second
        // loop pass, etc.

        println!("What is your temperature?");

        // Here temp refers to the outer one (outside of the loop)
        io::stdin().read_line(&mut temp).expect("Failed to read the line");

        // Shadowed temp = let's call it inner temp
        let temp = temp.trim().parse::<f32>();
        //    ^      ^
        //    |      |- Outer temp
        //    |- New inner temp

        // temp refers to inner temp
        if !temp.is_ok() {
            println!("Not a number!");
        } else {
            // Inner temp goes out of scope
            break;
        }

        // Inner temp goes out of scope
    }

    // Here temp refers to outer one (String)
    temp
}

A2 - you can't return &str. @E_net4 posted a link to the answer why. However, you can return String. You can do something like this nn case you'd like to have a validated String:

fn gettemp() -> String {
    loop {
        println!("What is your temperature?");

        let mut temp = String::new();
        io::stdin()
            .read_line(&mut temp)
            .expect("Failed to read the line");

        let trimmed = temp.trim();

        match trimmed.parse::<f32>() {
            Ok(_) => return trimmed.to_string(),
            Err(_) => println!("Not a number!"),
        };
    }
}

I see couple of another problems in your code.

let temp = String::new();

Should be let mut temp, because you'd like to borrow mutable reference later (&mut temp in the read_line call).

Another issue is the loop & read_line. read_line appends to the String. Run this code ...

let mut temp = "foo".to_string();
io::stdin().read_line(&mut temp).unwrap();
println!("->{}<-", temp);

... and enter 10 for example. You'll see following output ...

->foo10
<-

... which is not what you want. I'd rewrite gettemp() in this way:

fn gettemp() -> f32 {
    loop {
        println!("What is your temperature?");

        let mut temp = String::new();
        io::stdin()
            .read_line(&mut temp)
            .expect("Failed to read the line");

        match temp.trim().parse() {
            Ok(temp) => return temp,
            Err(_) => println!("Not a number!"),
        };
    }
}

IMHO explicit return temp is much cleaner & readable (compared to suggested break out of the loop with a value).


A3 - Why we don't need to explicitly state <f32> in temp.trim().parse()

It's inferred by the compiler.

fn gettemp() -> f32 { // 1. f32 is return type
    loop {
        println!("What is your temperature?");

        let mut temp = String::new();
        io::stdin()
            .read_line(&mut temp)
            .expect("Failed to read the line");

        match temp.trim().parse() {
        // 4. parse signature is pub fn parse<F>(&self) -> Result<F, ...>
        //    compiler knows it must be Result<f32, ...>
        //    Result<f32, ...> = Result<F, ...> => F = f32
        //    F was inferred and there's no need to explicitly state it
            Ok(temp) => return temp,
            //  |                |
            //  |      2. return type is f32, temp must be f32
            //  |
            //  | 3. temp must be f32, the parse result must be Result<f32, ...>            
            Err(_) => println!("Not a number!"),
        };
    }
}

Upvotes: 1

Hong Jiang
Hong Jiang

Reputation: 2376

In your program, loop { ... } creates a new scope. The scope of the second temp starts where it's defined and ends when loop ends. See the following example:

fn main() {
    let a = 1;
    {
        let a = 2;
        println!("{}", a);
    }
    println!("{}", a);
}

This prints 2, 1。

If you want to return a string, use (the code is fixed according to the comment below):

fn gettemp() -> String {
    loop {
        let mut temp = String::new();
        println!("What is your temperature?");
        std::io::stdin().read_line(&mut temp).expect("Failed to read the line");
        temp = temp.trim().to_string();
        match temp.parse::<f32>() {
            Err(_) => println!("Not a number!"),
            _ => return temp,
        }
    }
}

&str is a borrowed reference. You cannot return a borrowed reference to a local variable which will be released when the function returns.

Upvotes: 0

phimuemue
phimuemue

Reputation: 36071

Regarding question 1, you can break out of the loop with a value:

fn gettemp() -> f32 {
    let mut temp = String::new();

    loop {
        println!("What is your temperature?");

        io::stdin().read_line(&mut temp).expect("Failed to read the line");

        let temp = temp.trim().parse::<f32>();

        if !temp.is_ok() {
            println!("Not a number!");
        } else {
            break temp.unwrap() // yield value when breaking out of loop
        }
    }
}

This way, the whole loop's value is the thing you passed along with break.

Regarding question 2, I am not sure if you really want to do this, because &str is a borrowed type. I think you want to return an String in this case which owns the data.

Upvotes: 1

Related Questions