Reference and lifetimes

Hello!
I have a problem with struct's lifetimes and a mutable method.
I don't know how fix it. Please help me.
Example:
Playground - click me!

#[derive(Debug)]
pub struct Decoder<'a>
{
    proto_spec: ProtoSpec,
    pub decoder_map: HashMap<FrameDecoder, &'a FrameObject>
}

impl<'a> Decoder<'a> 
{
    pub fn init_decoder_hashmap(&mut self)
    {
        let mut map = HashMap::new();
        
        for frame_object in self.proto_spec.frames.iter()
        {
            let decoder = FrameDecoder{mask:1, template:2};
            map.insert(decoder, frame_object);
        }
        self.decoder_map = map;
    }

    pub fn new_from(proto_spec: ProtoSpec) -> Decoder<'a>
    {
        let mut decoder = Decoder{ proto_spec, decoder_map: HashMap::new() };
        decoder.init_decoder_hashmap();
        decoder
    }
}

Error:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
  --> src/main.rs:42:52
   |
42 |         for frame_object in self.proto_spec.frames.iter()
   |                                                    ^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime defined on the method body at 38:33...
  --> src/main.rs:38:33
   |
38 |     pub fn init_decoder_hashmap(&mut self)
   |                                 ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
  --> src/main.rs:42:29
   |
42 |         for frame_object in self.proto_spec.frames.iter()
   |                             ^^^^^^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined on the impl at 36:6...
  --> src/main.rs:36:6
   |
36 | impl<'a> Decoder<'a> 
   |      ^^
note: ...so that the expression is assignable
  --> src/main.rs:47:28
   |
47 |         self.decoder_map = map;
   |                            ^^^
   = note: expected `HashMap<_, &'a FrameObject>`
              found `HashMap<_, &FrameObject>`

Replace &mut self with &'a mut self as parameter of init_decoder_hashmap.

I can do it:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=a86fa9e7cd0bbf412fc285d5baed1947
but I want init in constructor (pub fn new_from(proto_spec: ProtoSpec) -> Decoder<'a>).

I exporting the Decoder to C ++. For that, I have to have ProtoSpec in Decoder.

#[cxx::bridge]
mod ffi {
    #[namespace = "rust_part"]
    extern "Rust" 
    {
        type Decoder<'a>;
        unsafe fn create_decoder<'a>(proto_spec: &CxxString) -> Result<Box<Decoder<'a>>>;
    }
}

I have cleaned up the code a bit. You can look at the code on the playground.
Unfortunately, as you can see, Decoder is a struct that stores a reference into itself (it stores the ProtoSpec, and also references to FrameObject, which are actually owned by the ProtoSpec you are storing in the Decoder).
I don't know of any simple way to do this, which doesn't involve using unsafe or an external crate.
You are better off not storing proto_spec in Decoder. The following playground link demonstrates a way of doing this.

2 Likes

The solution @RedDocMD has provided is already really good. :heart:

Just to provide another solution how one could solve it:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=1d615b1254a2f8a6cd8e1e36cc386fb3

Instead of referencing your FrameObject directly in your HashMap, you can reference it via index into the vector of ProtoSpec.
Keep in mind that there is now one more level of indirection, so the logic should be well encapsulated to provide a good API (e.g. in my example, the decoder_map of Decoder is now private).

1 Like

Absolutely! And in fact, if you know that the ProtoSpec is never going to change, then this solution is both more convenient and represents the problem better.

1 Like

One more time: Playground
Is it not possible to use an fn init(&'a mut self) function in a constructor (fn new_from(proto_spec: ProtoSpec) -> Driver<'a>) ?

You cannot because you are still effectively storing a reference to a field of a struct within that struct. Then, you are returning the struct by value, that is, you are moving the struct, while it is still borrowed (albeit, it is borrowed within itself).
Shifting around the code to different structs won't change the fact, because the lifetime system is immune to that.
Also, please could you rustfmt your code? Rust-devs are so used to seeing code formatted in exactly one fashion (unlike C++/C/Java) that it's rather jarring to see anything else.

In general, on a type Foo<'a> a method fn bar(&'a mut self) is a very bad idea / huge antipattern. It’s almost unusable, and almost never what you actually want. The type of self in such a method would be &'a mut Foo<'a>.

The value of type Foo<'a> can only exist for as long as the lifetime 'a is, so that a &'a mut Foo<'a> reference borrows such a value for its entire (remaining) existence. You cannot do anything with that value after calling such a method bar on it, since it’s perpetually exclusively borrowed. You cannot call any other method after bar, be it a &mut self method or a &self one; you cannot move the value anymore, in particular you cannot return it either, so: no, you can’t use fn init(&'a mut self) function in a constructor fn new_from(proto_spec: ProtoSpec) -> Driver<'a>, and “replace &mut self with &'a mut self as parameter of init_decoder_hashmap” is a terrible suggestion.

Side-note: You cannot execute a destructor on a Foo<'a> anymore either after creating a &'a mut Foo<'a>, which is why when Foo<'a> implements Drop, it won’t allow creation of a &'a mut Foo<'a> reference to a local variable anymore at all. (The only way to create a &'a mut Foo<'a> in this case would be to use Box::leak on a Box<Foo<'a>>.)

4 Likes

@RedDocMD
EDIT:
I used Rc<T> for sharing. It is correct?
Please check:
Playground

I don't think you are appreciating the actual problem here.

  • You can't store a reference to a non-static local variable and then return it. This never works in Rust (or in C or in C++, for that matter). This never works because when you return the reference, the variable the reference points to ceases to exist.

  • You can't store a reference to a field of a struct within a struct without any non-trivial amount of effort. The idiomatic way of storing references to something in a struct is to store that something in a different struct, which outlives the one storing the reference,

1 Like

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.