jpernst/rental

Problem with using rent_mut

Closed this issue · 4 comments

Hi,

I'm currently trying to use rental for help building an interface of fluent-bundle (https://github.com/projectfluent/fluent-rs/blob/master/fluent-bundle/src/bundle.rs) so that the lifetime won't be exposed and able to compile to Web Assembly, Now I have the rental and part of the interface like this:

rental! {
    mod my_rentals {
        use super::*;

        #[rental(covariant)]
        pub struct LocalizationInterface {
            resources: Box<FrozenMap<String, Box<FluentResource>>>,
            bundles: HashMap<String, FluentBundle<'resources>>,
        }
    }
}

#[wasm_bindgen]
pub struct LocalizationInterface(my_rentals::LocalizationInterface);

#[wasm_bindgen]
impl LocalizationInterface {
    pub fn new() -> Self {
        Self(my_rentals::LocalizationInterface::new(
                Box::new(FrozenMap::new()),
                |_resources| { HashMap::new() }
        ))
    }

    pub fn add_bundle(&mut self, bundle_id: String, locales: String) {
        let bundle = FluentBundle::new(&[locales]);
        //let fields = self.0.all();
        //fields.bundles.insert(bundle_id, bundle);
        self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle));
    }
}

I was trying to use fields.bundles.insert(bundle_id, bundle) to insert a bundle to the bundles, but it gave me this error:

error[E0596]: cannot borrow `*fields.bundles` as mutable, as it is behind a `&` reference
     --> src/lib.rs:970:9
      |
  970 |         fields.bundles.insert(bundle_id, bundle);
      |         ^^^^^^^^^^^^^^ cannot borrow as mutable

For the resources, this problem can be avoided by using FrozenMap. But for bundles, it cannot use the FrozenMap since it has a lifetime in it. So I tried to use rent_mut to mutably borrow the bundles and insert a bundle in it, but I think I didn’t use it correctly and get this compile error:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
     --> src/lib.rs:971:35
      |
  971 |         self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle));
      |                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
  note: first, the lifetime cannot outlive the anonymous lifetime #3 defined on the body at 971:25...
     --> src/lib.rs:971:25
      |
  971 |         self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle));
      |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      = note: ...so that the expression is assignable:
              expected std::option::Option<FluentBundle<'_>>
                 found std::option::Option<FluentBundle<'_>>
  note: but, the lifetime must be valid for the method call at 971:9...
     --> src/lib.rs:971:9
      |
  971 |         self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle));
      |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  note: ...so type `std::option::Option<FluentBundle<'_>>` of expression is valid during the expression
     --> src/lib.rs:971:9
      |
  971 |         self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle));
      |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Can I get some help about how to use rent_mut correctly? (Or is there some other way to make the insert works?)

/cc @zbraniecki

My first hunch is that the problem is HashMap::insert, which returns the old value in an Option. Since this value has a lifetime param for the type you're using, the closure is trying to return that value using an internal rental lifetime, which is illegal. Try adding a semicolon or otherwise discarding the return value of insert to prevent it from leaving the closure.

Sorry, I think I understand what you said, but I'm not very familiar with rust. For "adding a semicolon", I'm not sure where should I put the semicolon in, I've tried several places to put it, but none of them compiled. And for "discarding the return value of insert", do you mean something like self.0.rent_mut(|bundles| bundles.insert(bundle_id, bundle).unwrap()); or self.0.rent_mut(|bundles| bundles.entry(bundle_id).or_insert_with(|| bundle));?

Sorry, I should have been more specific. I mean something like this:

self.0.rent_mut(|bundles| { bundles.insert(bundle_id, bundle); })

A closure with a single expression will try to return the value of that expression, which won't work in this case since that value has a rental lifetime attached. A semicolon in rust suppresses the return value of an expression, but we need to put the expression in braces first for proper syntax.

Thank you, now I actually understand how it works! I really appreciate your time and assistance!