If let expressions have implicit coercion

Hi,

I notice that if let expression have implicit coercion (which I was unaware of). Consider the below code:

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Even(u64);

impl Even {
    pub fn new(value: u64) -> Option<Even> {
       if value % 2 == 0{
        return Some(Even(value));
       } 
       return None
    }
}

fn main(){
    let mut even_implicit_coercion = Vec::new();
    let mut even_without_implicit_coercion = Vec::new();
    let min = 11;
    let max = 20;
    for i in min..=max {
        for j in i..=max {
            let product = i * j;
            let some_even = Even::new(product);
            if let Some(product) = some_even {  
                even_implicit_coercion.push(product);
            }
            if let Some(_product) = some_even {  
                even_without_implicit_coercion.push(product);
            }
        }
    }

    println!("with implicit coercion: {:?}", even_implicit_coercion); //  [Even(132), Even(154), ... , Even(400)]
    println!("without implicit coercion: {:?}", even_without_implicit_coercion); // [132, 154, ... , 400]

}

I have no idea why this happens. Does anyone know why?

You pushed wrong variable.

That's why relying on shadowing is usually a bad idea, even if the language allows that. It exists for simple copies and for macro authors, but using it willy-nilly is just inviting subtle errors.

I'm sorry but don't understand why I am shadowing the variable product in the if let statement?

let product = i * j;
let some_even = Even::new(product);
if let Some(product) = some_even {...}  // shadowing occurs 

My understanding is that if let permits patterns matching. So it shouldn't shadow any variable, unlike:

let product = i * j;
let product = Even(product); 

Because you cannot match on variables in patterns (including match and if let), you can only match on constants, and create new variable bindings. Since if let-chains are still unstable, let's rewrite your code with match:

let product = i * j;
let some_even = Even::new(product);
match some_even {
    Some(x) => {
        let product = x;
        /* do stuff */
    }
    ...

That's what your code is equivalent to. If you need to compare the contents of Some with a variable, you need to either do the comparison explicitly in the match arm, or use a match guard:

let product = i * j;
let some_even = Even::new(product);
match some_even {
    Some(x) if x == product => {
        /* do stuff */
    }
    ...

The reason you can't match against variables is that match expressions are optimized to highly efficient variant selection, which wouldn't be possible with arbitrary equality comparisons, and also because == operator is overloadable and can execute arbitrary code. This would make it impossible to guarantee match exhaustiveness checking. While you can still run the same code in match guards, you are explicitly opting into more complicated semantics.

The binding shadowing footgun is exactly why the naming convention for constants is SCREAMING_CASE. This way it is immediately distinguishable whether a symbol in a match pattern means comparison with a constant or a new variable binding.

3 Likes

thank you for your detailed answer :slight_smile:

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.