Why I encounter borrow rule problem here?

Hi everyone,

I apologize if my question seems trivial as I am new to this platform and the Rust language.
I've got the "cannot borrow self.reservations as mutable because it is also borrowed as immutable" error when trying to implement a simple project.

here is my code snippet:

pub struct LicenseRepository<'a> {
    features: Vec<Feature>,
    plans: Vec<Plan<'a>>,
    reservations: Vec<LicenseReservation<'a>>,
    licenses: Vec<License<'a>>,
}

impl<'a> LicenseRepository<'a> {
    pub fn find_feature_by_code(&self, code: &str) -> Option<&Feature> {
        self.features.iter().find(|f| f.code == code)
    }

    pub fn find_plan_by_code(&self, code: &str) -> Option<&'a Plan> {
        self.plans.iter().find(|p| p.code == code)
    }

    pub fn add_feature(&mut self, feature: Feature) -> Result<(), Error> {
        if self.find_feature_by_code(&feature.code).is_some() {
            Err(Error::from("the code already exists"))
        } else {
            self.features.push(feature);
            Ok(())
        }
    }

    pub fn add_plan(&mut self, plan: Plan<'a>) -> Result<(), Error> {
        if self.find_plan_by_code(&plan.code).is_some() {
            Err(Error::from("the code already exists"))
        } else {
            self.plans.push(plan);
            Ok(())
        }
    }

    pub fn add_reservation(&'a mut self, user_id: Uuid, plan_code: &str, duration: Duration, customized_features: Option<Vec<CustomizedFeature<'a>>>) -> Option<u64> {
        let plan = self.find_plan_by_code(plan_code)?;
        let new_id: u64 = (self.reservations.len() + 1) as u64;
        let reservation = LicenseReservation::new(new_id, user_id, plan, duration, customized_features);

        self.reservations.push(reservation);

        Some(new_id)
    }
}

and here is the error:

error[E0502]: cannot borrow `self.reservations` as mutable because it is also borrowed as immutable
  --> src/repository.rs:48:9
   |
16 | impl<'a> LicenseRepository<'a> {
   |      -- lifetime `'a` defined here
...
44 |         let plan = self.find_plan_by_code(plan_code)?;
   |                    ---------------------------------
   |                    |
   |                    immutable borrow occurs here
   |                    argument requires that `*self` is borrowed for `'a`
...
48 |         self.reservations.push(reservation);
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

I've manipulated the code a lot to fix it, but it's not working as I expected, so I decided to ask some experts here about this issue. By the way, why 'a is necessary in the &'a mut self parameter of add_reservation method? If I omit it, it gives me an error saying "lifetime may not live long enough" for that same line (let plan = self.find_plan_by_code(plan_code)?;).

Thank you for reading my question and helping out

By writing &'a mut self, you've created a borrow of type &'a mut LicenseRepository<'a>, which says that self must be borrowed for as long as 'a, the lifetime that's also used in the struct type. You don't want that, because it means the struct is exclusively borrowed for the rest of its own existence, which is a mostly useless thing. Don't ever write that. If you find yourself seeming to need it, you have a problem earlier.

In this case, the problem is that you're trying to have a LicenseRepository store references to other data that is owned by the same struct. That will always give you problems; don't do that. In general, don't store references in your application's data structures; to a first approximation, references are meant only for temporary uses.

If you are concerned about duplication, you can use Rc or Arc to share data instead of referencing or cloning it. Or, it may be better to store some ID/key value to use in lookups instead.

3 Likes
impl<'a> LicenseRepository<'a> {
    pub fn add_reservation(&'a mut self, user_id: Uuid, plan_code: &str, duration: Duration, customized_features: Option<Vec<CustomizedFeature<'a>>>) -> Option<u64> {

You're borrowing yourself forever.

The compiler probably incorrectly pointed you in this direction (telling you to add 'a to &mut self) because you're trying to create a self-referential struct by storing a borrow you get from find_plan_by_code into self.reservations.

The correct high-level advice is "don't create self-referential structs". Probably you are overusing references/lifetimes. Store owned objects instead. Rust types with lifetimes are generally for short-lived borrows, not long-term storage.

2 Likes

Thank you for your helpful response. I've updated my question with the struct details:

pub struct LicenseRepository<'a> {
    features: Vec<Feature>,
    plans: Vec<Plan<'a>>,
    reservations: Vec<LicenseReservation<'a>>,
    licenses: Vec<License<'a>>,
}

the Plan actually contains a list of features that are stored as owned separately, so I reference to them in Plan. LicenseReservation contains a reference to Plans.
Is this a good approach to have all this information together in one struct?

That is exactly what you must not do. This is not a feasible use of references. Forget references exist, and design a data structure that does not use them.

2 Likes

Thank you for your helpful answer. You are correct. The compiler suggested that I add that 'a to the &'a mut self parameter. Could you please take a look at my response to @kpreid answer here and give me a suggestion about structure here?

Replace &'a Feature and the like with Rc<Feature> or Arc<Feature>.

Or perhaps have features: HashMap<FeatureId, Feature> and replace &'a Feature with FeatureId.

There are many approaches, but the key idea is that if you have different parts of your data structure "pointing" at each other, don't use references to do the "pointing".[1] Instead use shared ownership pointers like Rc/Arc, use indices or other identifiers, etc.


  1. &_ or &mut _ or really, anything with a lifetime ↩ī¸Ž

2 Likes

Yeah, that's right! Thank you for explaining the issue and helping me :pray:

Thank you for bringing up the main issue. You are absolutely right :pray: I mistakenly approached this situation with the mindset of other programming languages.

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.