user3918985
user3918985

Reputation: 4409

How do I access a variable outside of an `if let` expression?

I'm trying to access command line arguments. If the argument exists, do something, if not, do nothing. I have this code:

fn main() {
    if let Some(a) = std::env::args().nth(2) {
        let b = a;
    }

    println!("{}", a);
}

I am not able to access a or b outside of this scope:

error[E0425]: cannot find value `a` in this scope
 --> src/main.rs:6:20
  |
6 |     println!("{}", a);
  |                    ^ not found in this scope

How do I resolve this? Is there a better way to approach what I'm trying to do?

Upvotes: 5

Views: 7777

Answers (3)

Kushagra Gupta
Kushagra Gupta

Reputation: 604

I agree that you should either do whatever you want to do inside the if-else blocks. If you just to not indent your code and your else block is small, you can inverse your if-condition.

Instead of

let arg = std::env::args().nth(2);
if let Some(a) = arg {
    let b = a;
    // do stuff
} else {
    // do other stuff
}

you can do this

let arg = std::env::args().nth(2);
if arg.is_none() {
    // do other stuff
}
let b = arg.unwrap();
// do stuff

If you want the variable to be available in the outer scope, but assign it a value in an inner scope, you can declare it outside.

let arg = std::env::args().nth(2);
let a; // declared but not assigned
if let Some(b) = arg {
    a = b; // wasn't mut but first assignment can be done here
} else {
    a = "Foobar".to_string();
}
println!("{}", a); // available here but was assigned the value inside if

Upvotes: 2

oli_obk
oli_obk

Reputation: 31173

For an understanding of what's going on, have a look at the answer by Shepmaster

I'm not sure what you expect to happen in case the condition is not met. If you just want to abort the program in that case, the idiomatic Rust way is to use unwrap or expect.

// panics if there are fewer than 3 arguments.
let a = env::args().nth(2).unwrap();

println!("{}", a);

If you want a default value in case the argument does not exist, you can use unwrap_or.

// assigns "I like Rust" to a if there are fewer than 3 arguments
let a = env::args().nth(2).unwrap_or("I like Rust".to_string());

println!("{}", a);

As a further alternative, you can use the feature that in Rust almost everything is an expression:

let b = if let Some(a) = env::args().nth(2) {
    a
} else {
    // compute alternative value
    let val = "some value".to_string();
    // do operations on val
    val
};
println!("{}", b);

The idiomatic Rust way to write that would be with closures and unwrap_or_else:

let b = env::args().nth(2).unwrap_or_else(|| {
    // compute alternative value
    let val = "some value".to_string();
    // do operations on val
    val
});
println!("{}", b);

Upvotes: 12

Shepmaster
Shepmaster

Reputation: 430614

In Rust, it's always useful to ask yourself "what is the type of this variable?". Let's run with your example and pretend it works:

let arg = Some(true);

if let Some(a) = arg {
    let b = a;
}

println!("{:?}", b);

What is the type of b at the println line? Maybe you'd like it to be a boolean, but what is the type if the if clause doesn't pass? There isn't one! In other languages, maybe that would be nil or null, but Rust encodes that information with the Option type - that's where Some and None come from!

Additionally, scopes are very meaningful in Rust. Items defined inside a scope don't leak outside that scope:

{
    let a = 4;
}

println!("{}", a); // NOPE!

Between the two of these pieces, the code you want to write isn't possible. I agree that the right solution is to embed the println inside the if:

let arg = Some(true);

if let Some(a) = arg {
    println!("{:?}", a);
}

Upvotes: 7

Related Questions