Borrows and moves

I have this code:

let mut hs: Vec<(HandScore, Hand, &str)> =
    hands // &[&str]
    .iter()
    .map(|s| {
        let h = Hand::from(*s);
        let hs = HandScore::from(&h);
        (hs, h, *s)
    })
    .collect();

This gives me:

error[E0515]: cannot return value referencing local variable `h`
   --> src/lib.rs:468:13
    |
467 |             let hs = HandScore::from(&h);
    |                                      -- `h` is borrowed here
468 |             (hs, h, *s)
    |             ^^^^^^^^^^^ returns a value referencing data owned by the current function

This would normally be a reasonable complaint, but the compiler has enough info that h and hs lifetimes will become the same (since I'm moving them both into a tuple).

How do I tell rustc what I have in my head?

PS: sorry in advance if there's a duplicate topic. I've tried to look for it, but it's hard to find the words to describe this. :x

From what I can tell, HandScore is really HandScore<'_>, and you're creating a new owned Hand (h), then calculating a borrowed score which contains a reference to h (hs), and finally trying to return them both from the closure. This makes the tuple (hs, h, ...) a self-referential struct. Rust doesn't support directly self-referential structs (i.e. those creating with &). For example, when you move the tuple, you move h, and so you may move something that hs is referencing (pointing to), and then you would have a dangling reference.

If I'm correct, you could instead collect a Vec<Hand> (or (Hand, &str)), and then create a separate Vec<HandScore<'_>> that references into the Vec<Hand>. (I incidentally also suggest using #![deny(elided_lifetimes_in_paths)] or at least trying to get into the habit of always typing HandScore<'_> instead of HandScore, as it highlights the borrowing nature of the struct.)

If I'm wrong in my guess, please supply more code, e.g. the definitions of Hand, HandScore, and their From implementations.

2 Likes

No, you're spot on, even down to my workaround! :ok_hand:

Here it is, for completeness:

    let hands_v: Vec<Hand> = hands.iter().map(|s| Hand::from(*s)).collect();

    let mut hs: Vec<(HandScore, usize)> = hands_v
        .iter()
        .enumerate()
        .map(|(i, h)| {
            (HandScore::from(h), i)
        })
        .collect();

    // Access the string with hands[i]

This makes the tuple (hs, h, ...) a self-referential struct.

This was the missing part for me. It wasn't super obvious this was self referential. It's certainly not from the HandScore perspective, but it's certainly so from the tuple's perspective.

Thank you so much for the help (and the elided_lifetimes_in_paths tip)! :heart:

For example, when you move the tuple, you move h, and so you may move something that hs is referencing (pointing to), and then you would have a dangling reference.

If I may abuse the hand you extended, this feels like a solveable problem, tho. As long as h survives and hs, how could I get a dangling reference?

My question is actually answered here: std::pin - Rust

Again, thank you, @quinedot!

1 Like

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.