0

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
Herohtar
  • 5,347
  • 4
  • 31
  • 41
John Doe
  • 113
  • 1
  • 2
  • 11
  • The thread-safe version of `Rc` is `Arc`. Do you have dynamic actor names, though, or could they be `&'static str`? – Ry- Sep 16 '22 at 20:21
  • https://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct – PitaJ Sep 16 '22 at 20:40
  • @Ry- 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()`. Otherwise, I would have to retrieve the correct actor from `app_state.actors` based on the name and then call it. Regarding `Rc`, I have `pub struct App { state: Arc> }` so I thought initially that an additional `Arc` was not necessary. I will try it out with wrapping `Automotive(AutomotiveActor),` with `Arc`. – John Doe Sep 17 '22 at 09:19

1 Answers1

1

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 => {}
    }
}
Finomnis
  • 18,094
  • 1
  • 20
  • 27