Don't understand where a shared reference is

I have a function that looks like the following (it compiles properly):

pub async fn github<'a>(
    releases: &'a Vec<Release>,
    ...
) -> Option<&'a Asset> {
    for release in releases {
        for asset in &release.assets {
            if ... {
                return Some(asset);
            }
        }
    }
    None
}

And use it in another function like so:

pub async fn get_latest_compatible_asset<'a>(
    releases: Vec<Release>,
    ...
) -> Option<(Asset, bool)> {
    match check::github(
        &releases,
        ...
    )
    .await
    {
        Some(some) => Some((*some, false)),
//                          ^^^^^ is behind shared reference, cannot be moved
        None => {
            if ... {
                check::github(
                    &releases,
                    ...
                )
                .await
                .map(|some| (*some, true))
//                           ^^^^^ is behind shared reference, cannot be moved
            } else {
                None
            }
        },
    }
}

Why can't I move some?

  1. I own releases which is what asset borrows from
  2. The reference I gave to the check function should be 'finished' right?

So I should be able to move the asset from releases because no one else is using it. Why can't I? Is this yet another borrow checker bug, because I've been running into these way more than expected?

Are there alternative solutions to this? I can't let the check functions consume releases either because I have to use it twice.

Think about it like this:

  • asset is borrowed from a certain release
  • release is borrowed from releases slice
  • releases slice is borrowed from release Vec
  • Therefore, asset is borrowed from release Vec

So, since the release Vec remains valid after you call check::github, you cannot move out of asset, since that would make a certain entry of the vector invalid, hence the vector invalid.
You have to clone the asset, either in the match statement or while returning from check::github.

3 Likes

Why can't I dereference the release and invalidate the vector? I don't need it anymore. Also the library I'm trying to migrate to doesn't implement Clone for its structs (issue)

I think such partial invalidations are a limitation of the borrow checker. Look at what @H2CO3 said.

Yeah, that sucks. You should probably fork it if you are in a hurry.

You can't move out of references because then the reference would point to nothing, which is a violation of the memory model of the language.

You can't "invalidate" the vector because it is still being borrowed. It shouldn't matter for the semantics of a reference whether you create it from something that is inside your function or from something that is out of your control. If transitively invalidating references were allowed based on this, it would be very confusing, because the information required for that analysis is non-local.

Incidentally, "grab a part of this and invalidate the whole" is ownership, so you are simply looking for another language construct, not a reference. If you are trying to perform actions on a vector which require that you have ownership on the vector, then give out and acquire ownership. You could change the signature of github() to fn github(_: Vec<Release>) -> Opion<Asset> so that it would give you back the Asset by-value, consuming the vector of Releases.

4 Likes

I did try this but...

You wrote:

Which contradicts

So I'm not sure anymore what you are trying to do. If you indeed need the vector for more than one operation, and you are trying to remove a single element from it, then pass a mutable reference (&mut Vec<Release>) and call remove() or swap_remove() on it.

3 Likes

Indeed, I now realise this is more of a logical error on my side!

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.