Does "match" store values into a temporary variable before letting me access it?

I'm very new to Rust and am trying to understand why the following fails to compile:

fn main() {
    for n in 0..52 {        
        let su = match n / 13 {
            0 => "♠",
            1 => "♥",
            2 => "♣",
            3 => "♦",
            _ => "",
        };

        let fi = match n % 13 {
            0     => "A",
            10    => "J",
            11    => "Q",
            12    => "K",
            1..=9 => &((n % 13) + 1).to_string(),
            _     => "",
        };

        println!("{0}{1:>2}", su, fi);
    }
}

This returns "temporary value dropped while borrowed" and suggests me to "consider using a let binding to create a longer lived value" which I thought I did with the match statement assigning to the fi variable directly ?
Seeing as let fi = &((n % 13) + 1).to_string(); does work and compile, I'm guessing match stores values temporarily somewhere before assigning it to the fi variable ?

Would there be a way for me to neatly resolve this ?

With &((n % 13) + 1).to_string() you are creating a new String (with ((n % 13) + 1).to_string()) and then you'taking a reference to it with the & in front. You are then assigning that reference to fi, but the String is never assigned to a let binding, and in fact will be dropped when the scope in which it is defiined (the match arm) ends.

This works only thanks to temporary lifetime extension, which automatically assigns the ((n % 13) + 1).to_string() to a hidden let binding.


To make the match work you have a couple of options:

  • hardcode the numbers from 1 to 9 in the match (kinda repetitive, but it's the most efficient solution)

            let fi = match n % 13 {
                0     => "A",
                10    => "J",
                11    => "Q",
                12    => "K",
                1 => "1",
                2 => "2",
                3 => "3",
                4 => "4",
                5 => "5",
                6 => "6",
                7 => "7",
                8 => "8",
                9 => "9",
                _     => "",
            };
    
  • assign the string to a let binding defined outside the match before taking a reference to it:

            let num;
            let fi = match n % 13 {
                0     => "A",
                10    => "J",
                11    => "Q",
                12    => "K",
                1..=9 => {
                    num = ((n % 13) + 1).to_string();
                    &num
                }
                _     => "",
            };
    
  • always declare and assign the string outside the match (most ergonomic but the least efficient):

            let num = ((n % 13) + 1).to_string();
            let fi = match n % 13 {
                0     => "A",
                10    => "J",
                11    => "Q",
                12    => "K",
                1..=9 => &num,
                _     => "",
            };
    
5 Likes

use this is a crazy idea

let fi = match n % 13 {
                0     => "A",
                10    => "J",
                11    => "Q",
                12    => "K",
                1..=9 => {
                    let temp = format!("{}", n);
                    Box::leak(temp.into_boxed_str());
                    &temp
                }
                _     => "",
            };
            println!("{0}{1:>2}", su, fi);

So... If I understand correctly ((n % 13) + 1).to_string() returns a String that I need to store somewhere before being able to assign a reference to it to a variable ?
And my problem was that match didn't allow the same leniency that the let fi = &((n % 13) + 1).to_string(); statement provided but I'm guessing that would be the proper way to use Rust (assigning the String to properly store it then creating a reference to it)

Thanks for the quick and detailed response

I don't really know or understand Box::leak yet, does this allow the let declared within the match statement to be still valid outside of it ?

Using Box::leak is a bad idea in general, as it leaks memory (as the name suggests). Don't use it for this scenario definitely.

I didn't really intend to, it sounds like it goes against the philosophy of Rust (or my understanding of it, at least) I just wanted to better understand the idea behind the suggestion

Thank you all for the explanations and ideas

In this particular case you could just use chars instead of strings. You could use e.g T or a unicode character like “⒑” for ten.

fn main() {
    for n in 0..52 {        
        let su = match n / 13 {
            0 => "♠",
            1 => "♥",
            2 => "♣",
            3 => "♦",
            _ => "",
        };

        let fi = match n % 13 {
            0     => 'A',
            10    => 'J',
            11    => 'Q',
            12    => 'K',
            9      => '⒑',
            x @ 1..=8 =>char::from_digit(x + 1, 10).unwrap(),
            _     => unreachable!(),
        };

        println!("{0}{1}", su, fi);
    }
}
4 Likes