Brandon Kauffman
Brandon Kauffman

Reputation: 1864

Rust lifetimes in if statement

I have an if statement in a for loop, and I want it to create a variable with the lifetime of that iteration of the for loop.

    for condition_raw in conditions_arr {
        println!("{}", condition_raw);
        let matching = !condition_raw.contains('!');
        if matching {
            let index = condition_raw.find('=').unwrap_or_default();
        } else {
            let index = condition_raw.find('!').unwrap_or_default();
        }
        let key = &condition_raw[..index];
    }

let key = &condition_raw[..index]; currently throws cannot find value index in this scope not found in this scope rustc E0425

Upvotes: 1

Views: 243

Answers (2)

jthulhu
jthulhu

Reputation: 8688

I'll ignore the condition variable which does not seem to be used at all in your example.

A let statement creates a binding that holds at most for the current scope. For this reason, when you create the index variable inside the if, you are not making it accessible anywhere else. There are two ways to solve this issue.

The first way is to explicitly declare index as being part of the outer scope, and only define it inside the if statement.

for condition_raw in conditions_arr {
    let matching = !condition_raw.contains('!');
    let index;
    if matching {
        index = condition_raw.find('=').unwrap_or_default();
    } else {
        index = condition_raw.find('!').unwrap_or_default();
    }
    let key = &condition_arr[..index];
}

There is no risk of accidentally not defining index, since Rust will make sure that index is defined (exactly once) in all possible branching of your code before it is used. Yet, it's not a pretty solution because it violates a "locality" principle, that is that pieces of code should have effects on or pieces of code that are sufficiently close. In this case, the let index; is not too far from its definition, but it could be arbitrarily far, which makes it painful for someone who reads your code to remember that there is a declared but not yet defined.

Alternatively, you could use the fact that most things in Rust are expressions:

for condition_raw in conditions_arr {
    let matching = !condition_raw.contains('!');
    let index = if matching {
        condition_raw.find('=').unwrap_or_default();
    } else {
        condition_raw.find('!').unwrap_or_default();
    }
    let key = &condition_arr[..index];
}

But, in fact, you could factorize your code even more, which is usually better:

for condition_raw in conditions_arr {
    let matching = !condition_raw.contains('!');
    let index = condition_raw.find(if matching {
        '='
    } else {
        '!'
    }).unwrap_or_default();
    let key = &condition_arr[..index];

Or, even more

for condition_raw in conditions_arr {
    let index = condition_raw
        .find('!')
        .or_else(|| condition_raw.find('='))
        .unwrap_or_default();
    let key = &condition_arr[..index];
}

Upvotes: 3

Brandon Kauffman
Brandon Kauffman

Reputation: 1864

An idiomatic way to assign variables from an if else statement is as follows:

let index: usize = if matching {
            condition_raw.find('=').unwrap_or_default()
        } else {
            condition_raw.find('!').unwrap_or_default()
        };

Idiomatic way of assigning a value from an if else condition in Rust

In Rust, an if/else block is an expression. That is to say, the block itself has a value, equivalent to the last expression in whatever section was executed. With that in mind, I would structure your code like this:

Upvotes: 1

Related Questions