jpernst / rental

Rust macro to generate self-referential structs
Apache License 2.0
211 stars 11 forks source link

string cache lifetime problem #17

Closed MarkSwanson closed 7 years ago

MarkSwanson commented 7 years ago

I had the idea of creating a string cache using rental. I'm not quite able to get it to work - but I believe my code is correct in spirit. :-) ? The problem is with this single line of code:

self.add_values.insert(ref_k, new_vec);

Rustc error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
   --> src/bin/main.rs:351:39
    |
351 |                     add_values_struct.add(k, v);
    |                                       ^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #2 defined on the body at 350:58...
   --> src/bin/main.rs:350:58
    |
350 |                   let add_values_ref = add_values.rent_mut(|add_values_struct| {
    |  __________________________________________________________^
351 | |                     add_values_struct.add(k, v);
352 | |                 });
    | |_________________^
note: ...so that reference does not outlive borrowed content
   --> src/bin/main.rs:351:21
    |
351 |                     add_values_struct.add(k, v);
    |                     ^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the anonymous lifetime #3 defined on the body at 350:58...
   --> src/bin/main.rs:350:58
    |
350 |                   let add_values_ref = add_values.rent_mut(|add_values_struct| {
    |  __________________________________________________________^
351 | |                     add_values_struct.add(k, v);
352 | |                 });
    | |_________________^
note: ...so that types are compatible (expected &mut AddValue<'_>, found &mut AddValue<'_>)
   --> src/bin/main.rs:351:39
    |
351 |                     add_values_struct.add(k, v);
    |                                       ^^^

error: aborting due to previous error
pub struct AddValue<'a> {
    pub strings: &'a RefCell<HashSet<String>>,
    pub add_values: HashMap<&'a String, Vec<&'a String>>
}
impl<'a> AddValue<'a> {
    pub fn add<'b>(&'a mut self, k: &'b str, v: &'b str) {
        // convert to owned and insert
        let ref mut strings = self.strings.borrow_mut();
        strings.insert(k.to_string());
        strings.insert(v.to_string());
        // get references from *strings*
        let ref_k = strings.get(k).unwrap();
        let ref_v = strings.get(v).unwrap();
        let mut new_vec = Vec::new();
        new_vec.push(ref_v);
        // insert into add_values (k and v are not used!)
        // k or v could Drop at this point and we wouldn't care.
        // Why does Rust think k and v are still required to live?
        self.add_values.insert(ref_k, new_vec);
    }
}

rental! {
    #[allow(non_camel_case_types)]
    pub mod string_test {
        use std::collections::{HashMap, HashSet};
        use std::rc::Rc;
        use std::cell::RefCell;
        use super::AddValue;

        #[rental]
        pub struct StringsAddValues {
            strings: Rc<RefCell<HashSet<String>>>,
            add_values: AddValue<'strings>
        }
    }
}

struct WS {
    strings: Rc<RefCell<HashSet<String>>>,
    add_values: Option<string_test::StringsAddValues>,
}
impl WS {
    fn new() -> WS {
        let strings_rc = Rc::new(RefCell::new(HashSet::new()));
        WS {
            strings: strings_rc.clone(),
            add_values: Some({
                string_test::StringsAddValues::new(
                    strings_rc.clone(),
                    |strings| {
                        let hm: HashMap<&String, Vec<&String>> = HashMap::new();
                        AddValue {
                            strings: strings,
                            add_values: hm
                        }
                    })
            }),
        }
    }

    fn add<'a, 'b>(&'a mut self, k: &'b str, v: &'b str) {
        match self.add_values {
            Some(ref mut add_values) => {
                let add_values_ref = add_values.rent_mut(|add_values_struct| {
                    add_values_struct.add(k, v);
                });
            },
            None => panic!("Not initialized properly")
        }
    }
}
MarkSwanson commented 7 years ago

Thoughts: sometimes macro error messages are wildly misleading - and I believe this is one of those times. I see you're doing essentially this in your complex_mut example. Maybe I just need to alter how I construct AddValue? Will circle back in a bit.

MarkSwanson commented 7 years ago

I'm still unable to get this to work. In a nutshell I would like to have multiple collections that contain references to Strings in a single global collection. Thoughts?

jpernst commented 7 years ago

Holding references to things inside the collection effectively means you won't be able to add anything to the collection after that point. So unless you're adding all your strings beforehand and then never modifying the collection again, holding direct references to the strings probably isn't going to work.

MarkSwanson commented 7 years ago

A String provides a stable address so as long as I never remove anything from the collection I should be good right? (the single backing store collection isn't really global - I just meant it will outlive any collections that refer to it. This lets me 'garbage collect' the backing store at the end of the process/transaction. Edit: my smaller 'reference' collections are now storing *const u8 + len to the String data (boxed stable address). I thought it would be nice if they would be &str - but I haven't checked to see if &str points to the boxed data or the String... I should probably check that :-)

MarkSwanson commented 7 years ago

After thinking about it, of course the ref is unstable and different from the internal boxed data. My string cache using *const +len is working fine - and I have no need to use rental to manage that cache. Closing...