John Doe
John Doe

Reputation: 145

Store reference of object in field of struct

I have the following struct:

#[derive(Default)]
pub struct AppState {
    actors: HashMap<String, ActorValue>,
    feature: HashMap<String, FeatureValue>,
}

Actors are registered when running the application upon receiving a network request (i.e., they are inserted into the HashMap). Furthermore, a user can create a new feature for which a certain actor may be required.

pub enum ActorValue {
    Automotive(AutomotiveActor),
    Power(PowerActor),
}

pub enum FeatureValue {
    Automotive(AutomotiveFeature),
    // ....
}

pub struct AutomotiveFeature {
    pub actor_name: String,
    // ... more Actor-related String fields
}

pub struct AutomotiveActor {
    name: String,
    // ... more String fields
}

So, when creating an instance of AutomotiveFeature I am currently cloning the name of the respective AutomotiveActor instance to populate the actor_name:

let automotive_actor = app_state.actors.iter()
        .find(|x| matches!(x.1, ActorValue::Automotive(_)))
        .map(|x| match x.1 {
            ActorValue::Automotive(p) => Some(p),
            _ => None,
        })
        .flatten();

match automotive_actor {
        Some(a) => {
          let feature = AutomotiveFeature { actor_name: a.name.clone() };
        }
        None => {}
}

However, I am essentially keeping redundant info. Ideally, I could just replace all the String fields relating to the actor in the feature with a reference:

pub struct AutomotiveFeature {
        pub actor: &AutomotiveActor
    }

But I am getting lifetime issues and I don't know how I can annotate them correctly, considering I have two HashMaps.

If I use:

pub struct AutomotiveFeature {
        pub actor: &'static AutomotiveActor
    }

I get the following errors:

error[E0502]: cannot borrow `*state` as mutable because it is also borrowed as immutable
   --> crates/code/src/my_code.rs:146:13
    |
38  |       let automotive_actor: Option<&AutomotiveActor> = app_state
    |  __________________________________________________-
39  | |         .actors()
    | |_____________________________- immutable borrow occurs here
...
43  |               ActorValue::Automotive(p) => Some(p),
    |                                          ------- returning this value requires that `*state` is borrowed for `'static`
...
146 | /             app_state
147 | |                 .features_mut()
    | |____________________________^ mutable borrow occurs here

error: lifetime may not live long enough
  --> crates/code/src/my_code.rs:43:40
   |
35 |     app_state: &mut AppState,
   |            - let's call the lifetime of this reference `'1`
...
43 |             ActorValue::Automotive(p) => Some(p),
   |                                        ^^^^^^^ returning this value requires that `'1` must outlive `'static`

I have already looked at similar post, such as "Store reference of struct in other struct". Unfortunately, I cannot use std::rc::Rc; because I get the error:

`Rc<AutomotiveActor>` cannot be sent between threads safely

Upvotes: 0

Views: 969

Answers (1)

Finomnis
Finomnis

Reputation: 22808

I am getting lifetime issues and I don't know how I can annotate them correctly"

Note that you can only explain to the compiler how long something lives. You can't actually make an object live longer by annotating a lifetime. References do not own an object or keep it alive. Rc/Arc actually keep an object alive, so I have a suspicion that this is what you want.

The reason I want to have a reference is that I can then implement methods as part of AutomotiveActor and then directly call automotive_actor.start_car()

I suspect that start_car() modifies the automotive_actor and is therefore a mut fn. This completely renders your initial idea of using references impossible, because you can only ever have one mutable reference to an object.

Rc/Arc also only provide immutable access to the object, but you can combine them with RefCell/Mutex to create interior mutability.

Rc<AutomotiveActor> cannot be sent between threads safely

This makes me assume that your project is multi-threaded and therefore requires thread safety. This means you probably want to use Arc<Mutex>.

This is one possible layout:

use std::{
    collections::HashMap,
    sync::{Arc, Mutex},
};

#[derive(Default)]
pub struct AppState {
    actors: HashMap<String, ActorValue>,
    feature: HashMap<String, FeatureValue>,
}

pub enum ActorValue {
    Automotive(Arc<Mutex<AutomotiveActor>>),
    //Power(PowerActor),
}

pub enum FeatureValue {
    Automotive(AutomotiveFeature),
    // ....
}

pub struct AutomotiveFeature {
    pub actor: Arc<Mutex<AutomotiveActor>>,
    // ... more Actor-related String fields
}

pub struct AutomotiveActor {
    name: String,
    // ... more String fields
}

fn main() {
    let mut app_state = AppState::default();

    let new_actor = Arc::new(Mutex::new(AutomotiveActor {
        name: String::from("MyActor"),
    }));

    app_state.actors.insert(
        new_actor.lock().unwrap().name.clone(),
        ActorValue::Automotive(Arc::clone(&new_actor)),
    );

    let automotive_actor = app_state
        .actors
        .iter()
        .find(|x| matches!(x.1, ActorValue::Automotive(_)))
        .map(|x| match x.1 {
            ActorValue::Automotive(p) => Some(p),
            _ => None,
        })
        .flatten();

    match automotive_actor {
        Some(a) => {
            let feature = AutomotiveFeature {
                actor: Arc::clone(a),
            };
        }
        None => {}
    }
}

Upvotes: 1

Related Questions