Max888
Max888

Reputation: 3770

Return owned item and reference to item

I am using the git2 crate and would like to get Statuses for the repo and store this in my app struct for reuse later since it is expensive to create. The problem is that Statuses references the Repository from which it was created. As far as I understand from this question: Why can't I store a value and a reference to that value in the same struct?, I can't return an owned item along with a refence to it since the address of the owned item will change when it is returned from the function and moved, thereby making the reference invalid. The below is a minimal example of what I am trying to do, what is the correct way to tackle this?

use git2::{Repository, Statuses};

struct App<'a> {
    repo: Repository,
    statuses: Statuses<'a>,
}
impl<'a> App<'a> {
    fn new() -> Self {
        let repo = Repository::open("myrepo").unwrap();
        let statuses = repo.statuses(None).unwrap();
        App { repo, statuses }
    }
}

fn main() {
    let mydata = App::new();
    dbg!(mydata.statuses.len());
}

Below is the only solution I have found (also taken from the above question), which is to make Statuses optional and mutate the app data after Repository has already been returned from ::new(). This seems hacky and not idiomatic, and doesn't compile anyway.

use git2::{Repository, Statuses};

struct App<'a> {
    repo: Repository,
    statuses: Option<Statuses<'a>>,
}
impl<'a> App<'a> {
    fn new() -> Self {
        let repo = Repository::open("myrepo").unwrap();
        App {
            repo,
            statuses: None,
        }
    }
}

fn main() {
    let mut mydata = App::new();
    mydata.statuses = mydata.repo.statuses(None).ok();
    dbg!(mydata.statuses.unwrap().len());
}
error[E0597]: `mydata.repo` does not live long enough
  --> src/main.rs:19:23
   |
19 |     mydata.statuses = mydata.repo.statuses(None).ok();
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^ borrowed value does not live long enough
20 |     dbg!(mydata.statuses.unwrap().len());
21 | }
   | -
   | |
   | `mydata.repo` dropped here while still borrowed
   | borrow might be used here, when `mydata` is dropped and runs the destructor for type `App<'_>`

EDIT: Some additional context: I am making an app with egui, so the App struct is the application state. Amoung other things, it will list all git repos in a directory, and display information about their statuses. I measured the repo.statuses(None).unwrap() call and and for ~10 repositories it took a total of 4ms, so too slow to call on each loop of the app. The obvious solution I could think of was to store the data in the app's state (App) but that doesn't seem possible so I'm looking for alternative approaches.

Upvotes: 0

Views: 109

Answers (1)

Finomnis
Finomnis

Reputation: 22601

I think there are two solutions:

  • Copy the data you want out of the Statuses object and then release it
  • Use an external crate like self_cell to create a self-referential object. Note that this object can then no longer provide mut access to the Repository.

I'd argue that the first option would be the way to go in your case, because to my understanding, Statuses is simply a collection of paths with a status for each path.

use std::collections::HashMap;

use git2::{Repository, Status};

struct App {
    repo: Repository,
    statuses: HashMap<String, Status>,
}
impl App {
    fn new() -> Self {
        let repo = Repository::open(".").unwrap();
        let statuses = repo
            .statuses(None)
            .unwrap()
            .iter()
            .map(|el| (el.path().unwrap().to_string(), el.status()))
            .collect::<HashMap<_, _>>();
        App { repo, statuses }
    }
}

fn main() {
    let mydata = App::new();
    dbg!(mydata.statuses.len());
    println!("{:#?}", mydata.statuses);
}
[src/main.rs:24] mydata.statuses.len() = 2
{
    "Cargo.toml": WT_MODIFIED,
    "src/main.rs": INDEX_MODIFIED | WT_MODIFIED,
}

Upvotes: 1

Related Questions