I am trying to do error handling when using .iter() in combination with .flat_map, which has an .iter().map() within it.
The scenario is getting all the Events that belong to a set of organisations, where the nested .iter().map() is used to get the participants of each event, combine that with the event and return an EventResponse stuct.
A non-specific-scenario way of describing the issue would be 'How to get Result<Vec<T>, err> from nested flat_map of Result<Vec<T>, err>, which has nested map of Result<T, err>'
Below is an abstracted/simplified version of the code I'm using, that gives me the same errors as my actual code.
struct Event {
id: usize,
}
#[derive(Debug)]
struct EventResponse {
id: usize,
participants: Vec<i32>,
}
fn main() {
let orgs = vec![1, 3, 14, 12];
let events: Result<Vec<EventResponse>, &str> = orgs
.iter()
.flat_map::<Result<Vec<EventResponse>, &str>, _>(|org_id| {
get_events_for(*org_id)
.map_err(|_| "That Org id does not exist")
.map(|events| {
events
.iter()
.map::<Result<EventResponse, &str>, _>(|event| {
get_event_with_participants(event)
.map(|event_response| event_response)
.map_err(|_| "Participants for that event were not found")
})
.collect()
})
})
.collect();
}
fn get_events_for(id: usize) -> Result<Vec<Event>, ()> {
// pretend we are checking against a database of existing org ids, if the org id does not exist, then return an error
if id == 3 {
Ok(vec![Event { id }])
} else {
Err(())
}
}
fn get_event_with_participants(event: &Event) -> Result<EventResponse, ()> {
//pretend the participants are fetched from a database
let foundParticipants = true;
if foundParticipants {
Ok(EventResponse {
id: event.id,
participants: vec![1, 2, 5],
})
} else {
Err(())
}
}
Playground
The type annotations are to show what is expected to be returned at each stage. I expect events to be of type Result<Vec<EventResponse>, &str> but I am getting 2 errors:
error[E0277]: a collection of type `std::vec::Vec<EventResponse>` cannot be built from an iterator over elements of type `std::result::Result<EventResponse, &str>`
--> example.rs:27:26
|
27 | .collect()
| ^^^^^^^ a collection of type `std::vec::Vec<EventResponse>` cannot be built from `std::iter::Iterator<Item=std::result::Result<EventResponse, &str>>`
|
= help: the trait `std::iter::FromIterator<std::result::Result<EventResponse, &str>>` is not implemented for `std::vec::Vec<EventResponse>`
error[E0277]: a collection of type `std::result::Result<std::vec::Vec<EventResponse>, &str>` cannot be built from an iterator over elements of type `std::vec::Vec<EventResponse>`
--> example.rs:30:10
|
30 | .collect();
| ^^^^^^^ a collection of type `std::result::Result<std::vec::Vec<EventResponse>, &str>` cannot be built from `std::iter::Iterator<Item=std::vec::Vec<EventResponse>>`
|
= help: the trait `std::iter::FromIterator<std::vec::Vec<EventResponse>>` is not implemented for `std::result::Result<std::vec::Vec<EventResponse>, &str>`
EDIT: The get_events_for function cannot be modified, however, the get_event_with_participants function can be modified, if that helps.
Being interested in the problem, I tried to implement this with map and try_fold. Using flat_map directly didn't work for me, as I couldn't get around the fact that the first inner loop has to produce a Result. I moved the error messages into the functions, but that could easily be undone if you explicitly wanted to avoid it.
struct Event {
id: usize,
}
#[derive(Debug)]
struct EventResponse {
id: usize,
participants: Vec<i32>,
}
fn main() {
let orgs = vec![1, 3, 14, 12];
let events: Result<Vec<EventResponse>, &str> = orgs
.iter()
.map(|org_id| {
get_events_for(*org_id)?
.iter()
.map(get_event_with_participants)
.collect::<Result<Vec<_>, _>>()
})
.collect::<Result<Vec<_>, _>>()
.map(|org_responses| org_responses.into_iter().flatten().collect());
}
fn get_events_for(id: usize) -> Result<Vec<Event>, &'static str> {
// pretend we are checking against a database of existing org ids, if the org id does not exist, then return an error
if id == 3 {
Ok(vec![Event { id }])
} else {
Err("That Org id does not exist")
}
}
fn get_event_with_participants(event: &Event) -> Result<EventResponse, &'static str> {
//pretend the participants are fetched from a database
let foundParticipants = true;
if foundParticipants {
Ok(EventResponse {
id: event.id,
participants: vec![1, 2, 5],
})
} else {
Err("Participants for that event were not found")
}
}
For try_fold, the main function would become:
fn main() {
let orgs = vec![1, 3, 14, 12];
let events: Result<Vec<EventResponse>, &str> =
orgs.iter().try_fold(Vec::new(), |mut responses, &org| {
responses = get_events_for(org)?
.into_iter()
.try_fold(responses, |mut responses, event| {
let response = get_event_with_participants(&event)?;
responses.push(response);
Ok(responses)
})?;
Ok(responses)
});
}
Personally I'm a fan of the map version, as modifying a Vec using try_fold feels awkward since you have to return the accumulator despite modifying the Vec directly.
Another version to consider is to just use loops, which appears to be far simpler in this situation since you can use the ? operator for errors:
fn main() {
let orgs = vec![1, 3, 14, 12];
let events = get_all_responses(orgs);
}
fn get_all_responses(
orgs: impl IntoIterator<Item = usize>,
) -> Result<Vec<EventResponse>, &'static str> {
let mut responses = Vec::new();
for org in orgs.into_iter() {
for event in get_events_for(org)? {
responses.push(get_event_with_participants(&event)?)
}
}
Ok(responses)
}
Related
I want to capture the duration of execution of a span in rust tracing and send that as metric.
I have found that fmt() helps in printing that as mentioned here:How can I log span duration with Rust tracing?
I have also tried this example about creating layer and implementing on_new_span() and on_event(). I added on_close() as well to check what metadata do we get here. The code for that I wrote is:
use tracing::{info, info_span};
use tracing_subscriber::prelude::*;
mod custom_layer;
use custom_layer::CustomLayer;
fn main() {
tracing_subscriber::registry()
.with(CustomLayer)
.init();
let outer_span = info_span!("Outer", level = 0, other_field = tracing::field::Empty);
let _outer_entered = outer_span.enter();
outer_span.record("other_field", &7);
let inner_span = info_span!("inner", level = 1);
let _inner_entered = inner_span.enter();
info!(a_bool = true, answer = 42, message = "first example");
}
custom_layer.rs:
use std::collections::BTreeMap;
use tracing_subscriber::Layer;
pub struct CustomLayer;
impl<S> Layer<S> for CustomLayer
where
S: tracing::Subscriber,
S: for<'lookup> tracing_subscriber::registry::LookupSpan<'lookup>,
{
fn on_new_span(
&self,
attrs: &tracing::span::Attributes<'_>,
id: &tracing::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
let span = ctx.span(id).unwrap();
let mut fields = BTreeMap::new();
let mut visitor = JsonVisitor(&mut fields);
attrs.record(&mut visitor);
let storage = CustomFieldStorage(fields);
let mut extensions = span.extensions_mut();
extensions.insert(storage);
}
fn on_record(
&self,
id: &tracing::span::Id,
values: &tracing::span::Record<'_>,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
// Get the span whose data is being recorded
let span = ctx.span(id).unwrap();
// Get a mutable reference to the data we created in new_span
let mut extensions_mut = span.extensions_mut();
let custom_field_storage: &mut CustomFieldStorage =
extensions_mut.get_mut::<CustomFieldStorage>().unwrap();
let json_data: &mut BTreeMap<String, serde_json::Value> = &mut custom_field_storage.0;
// And add to using our old friend the visitor!
let mut visitor = JsonVisitor(json_data);
values.record(&mut visitor);
}
fn on_event(&self, event: &tracing::Event<'_>, ctx: tracing_subscriber::layer::Context<'_, S>) {
// All of the span context
let scope = ctx.event_scope(event).unwrap();
let mut spans = vec![];
for span in scope.from_root() {
let extensions = span.extensions();
let storage = extensions.get::<CustomFieldStorage>().unwrap();
let field_data: &BTreeMap<String, serde_json::Value> = &storage.0;
spans.push(serde_json::json!({
"target": span.metadata().target(),
"name": span.name(),
"level": format!("{:?}", span.metadata().level()),
"fields": field_data,
}));
}
// The fields of the event
let mut fields = BTreeMap::new();
let mut visitor = JsonVisitor(&mut fields);
event.record(&mut visitor);
// And create our output
let output = serde_json::json!({
"target": event.metadata().target(),
"name": event.metadata().name(),
"level": format!("{:?}", event.metadata().level()),
"fields": fields,
"spans": spans,
});
println!("{}", serde_json::to_string_pretty(&output).unwrap());
}
fn on_close(
&self,
id: tracing::span::Id,
ctx: tracing_subscriber::layer::Context<'_, S>,
) {
// Get the span whose data is being recorded
let span = ctx.span(&id).unwrap();
let output = serde_json::json!({
"target": span.metadata().target(),
"name": span.name(),
"level": format!("{:?}", span.metadata().level()),
"fields": format!("{:?}", span.metadata().fields()),
});
println!("On_close{}", serde_json::to_string_pretty(&output).unwrap());
}
}
struct JsonVisitor<'a>(&'a mut BTreeMap<String, serde_json::Value>);
impl<'a> tracing::field::Visit for JsonVisitor<'a> {
fn record_f64(&mut self, field: &tracing::field::Field, value: f64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_i64(&mut self, field: &tracing::field::Field, value: i64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_u64(&mut self, field: &tracing::field::Field, value: u64) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_bool(&mut self, field: &tracing::field::Field, value: bool) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_str(&mut self, field: &tracing::field::Field, value: &str) {
self.0
.insert(field.name().to_string(), serde_json::json!(value));
}
fn record_error(
&mut self,
field: &tracing::field::Field,
value: &(dyn std::error::Error + 'static),
) {
self.0.insert(
field.name().to_string(),
serde_json::json!(value.to_string()),
);
}
fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) {
self.0.insert(
field.name().to_string(),
serde_json::json!(format!("{:?}", value)),
);
}
}
#[derive(Debug)]
struct CustomFieldStorage(BTreeMap<String, serde_json::Value>);
Cargo.toml
[package]
name = "tracing-custom-logging"
version = "0.1.0"
edition = "2021"
[dependencies]
serde_json = "1"
tracing = "0.1"
tracing-subscriber = "0.3.16"
snafu = "0.7.3"
thiserror = "1.0.31"
tracing-opentelemetry = "0.18.0"
Unfortunately I have not been able to get the data about duration of a span anywhere. Can you guys help me identify how/where can I get it from?
You cannot "get" the span duration from the tracing crate because it doesn't store it. It only stores the basic metadata and allows for hooking into framework events in a lightweight way. It is the job of the Subscriber to keep track of any additional data.
You could use the tracing-timing crate if you only need periodic histograms. Otherwise, you can't really use data from an existing layer which may already store timing data, because they often don't expose it. You'll have to keep track of it yourself.
Using the tracing-subscriber crate, you can create a Layer and store additional data using the Registry. Here's an example of how that can be done:
use std::time::Instant;
use tracing::span::{Attributes, Id};
use tracing::Subscriber;
use tracing_subscriber::layer::{Context, Layer};
use tracing_subscriber::registry::LookupSpan;
struct Timing {
started_at: Instant,
}
pub struct CustomLayer;
impl<S> Layer<S> for CustomLayer
where
S: Subscriber,
S: for<'lookup> LookupSpan<'lookup>,
{
fn on_new_span(&self, _attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
let span = ctx.span(id).unwrap();
span.extensions_mut().insert(Timing {
started_at: Instant::now(),
});
}
fn on_close(&self, id: Id, ctx: Context<'_, S>) {
let span = ctx.span(&id).unwrap();
let started_at = span.extensions().get::<Timing>().unwrap().started_at;
println!(
"span {} took {}",
span.metadata().name(),
(Instant::now() - started_at).as_micros(),
);
}
}
This just prints out the results where they are calculated, but you can emit the results elsewhere, or store it in some shared resource as you see fit.
Some example usage:
use std::time::Duration;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
#[tracing::instrument]
fn test(n: u64) {
std::thread::sleep(Duration::from_secs(n));
}
fn main() {
tracing_subscriber::registry::Registry::default()
.with(CustomLayer)
.init();
test(1);
test(2);
test(3);
}
span test took 1000081
span test took 2000106
span test took 3000127
You may also need to be aware of on_enter() and on_exit(), which are relevant when using async functions because their execution may be suspended and resumed later, and you can use those functions to be notified when that happens. Depending on what you're looking for, you may need to add filtering so that you only track the spans you're interested in (by name or target or whatever).
I have a struct where I've derived a couple of things.
#[derive(PartialEq, Debug)]
struct Subscriber {
id: u16,
up_speed: u32,
down_speed: u32
}
However, when I try to use PartialEq, I get told it is not implemented.
for (id, subscriber) in &new_hashmap {
let original_subscriber = original_hashmap.get(id).unwrap();
if original_subscriber == None {
changed_hashmap.insert(subscriber.id, subscriber);
} else if subscriber != original_subscriber {
changed_hashmap.insert(subscriber.id, subscriber);
}
}
Here's the compiler error.
error[E0277]: can't compare `&Subscriber` with `Option<_>`
--> src/main.rs:34:32
|
34 | if original_subscriber == None {
| ^^ no implementation for `&Subscriber == Option<_>`
|
= help: the trait `PartialEq<Option<_>>` is not implemented for `&Subscriber`
= help: the trait `PartialEq` is implemented for `Subscriber`
If I rewrite it to not put original_subscriber into its own variable, then it works.
for (id, subscriber) in &new_hashmap {
if original_hashmap.get(id) == None {
changed_hashmap.insert(subscriber.id, subscriber);
} else if subscriber != original_hashmap.get(id).unwrap() {
changed_hashmap.insert(subscriber.id, subscriber);
}
}
The rest of the code is essentially doing the following.
Create HashMap of 2 Subscriber instances.
Create another HashMap of 3 Subscriber instances, 1 of which is new, 1 of which is the same, and 1 of which has the same key but an updated value.
That is original_hashmap HashMap and new_hashmap.
The goal is to get a third HashMap of items in new_hashmap that are new to original_hashmap or have changed values.
your code does not work for 2 reasons.
If you derive PartialEq it will only work for Subscriber == Subscriber checks. You need to implement PartialEq<Type>
You are using a reference when comparing. This means you need to implement PartialEq for &Subscriber and not subscriber
This should do the trick
#[derive(PartialEq, Debug)]
struct Subscriber {
id: u16,
up_speed: u32,
down_speed: u32,
}
let subscriber = Subscriber {
id: 1,
up_speed: 100,
down_speed: 100,
};
impl PartialEq<Option<Subscriber>> for &Subscriber {
fn eq(&self, other: &Option<Subscriber>) -> bool {
match other {
Some(other) => return other == *self,
None => return false,
}
}
}
if &subscriber == None {
println!("None");
} else {
println!("Some");
}
But I am not sure if this is really what you want. I will try to implement the same and edit my answer afterwards
I suppose that's what you want to implement
use std::collections::HashMap;
#[derive(Debug, PartialEq)]
struct Subscriber {
id: u16,
up_speed: u32,
down_speed: u32,
}
impl Subscriber {
fn new(id: u16, up_speed: u32, down_speed: u32) -> Subscriber {
Subscriber {
id,
up_speed,
down_speed,
}
}
}
fn main() {
let mut old_map = HashMap::new();
old_map.insert(1, Subscriber::new(1, 1, 1));
old_map.insert(2, Subscriber::new(2, 2, 2));
let mut new_map = HashMap::new();
new_map.insert(0, Subscriber::new(0, 0, 0)); //new
new_map.insert(1, Subscriber::new(1, 1, 1)); //Same
new_map.insert(2, Subscriber::new(3, 3, 3)); //Same key but different value
let mut changed_map = HashMap::new();
//
for (key, subscriber) in &new_map {
if old_map.contains_key(&key) {
if old_map[&key] != *subscriber {
changed_map.insert(key, subscriber);
}
} else {
changed_map.insert(key, subscriber);
}
}
println!("{:?}", changed_map);
}
It will return
{2: Subscriber { id: 3, up_speed: 3, down_speed: 3 }, 0: Subscriber { id: 0, up_speed: 0, down_speed: 0 }}
I used the deref operator to avoid impl PartialEq<Subscriber> for &Subscriber but you could have done that as well
I have created a simplified version of my problem below, I have a Bag struct and Item struct. I want to spawn 10 threads that execute item_action method from Bag on each item in an item_list, and print a statement if both item's attributes are in the bag's attributes.
use std::sync::{Mutex,Arc};
use std::thread;
#[derive(Clone, Debug)]
struct Bag{
attributes: Arc<Mutex<Vec<usize>>>
}
impl Bag {
fn new(n: usize) -> Self {
let mut v = Vec::with_capacity(n);
for _ in 0..n {
v.push(0);
}
Bag{
attributes:Arc::new(Mutex::new(v)),
}
}
fn item_action(&self, item_attr1: usize, item_attr2: usize) -> Result<(),()> {
if self.attributes.lock().unwrap().contains(&item_attr1) ||
self.attributes.lock().unwrap().contains(&item_attr2) {
println!("Item attributes {} and {} are in Bag attribute list!", item_attr1, item_attr2);
Ok(())
} else {
Err(())
}
}
}
#[derive(Clone, Debug)]
struct Item{
item_attr1: usize,
item_attr2: usize,
}
impl Item{
pub fn new(item_attr1: usize, item_attr2: usize) -> Self {
Item{
item_attr1: item_attr1,
item_attr2: item_attr2
}
}
}
fn main() {
let mut item_list: Vec<Item> = Vec::new();
for i in 0..10 {
item_list.push(Item::new(i, (i+1)%10));
}
let bag: Bag= Bag::new(10); //create 10 attributes
let mut handles = Vec::with_capacity(10);
for x in 0..10 {
let bag2 = bag.clone();
let item_list2= item_list.clone();
handles.push(
thread::spawn(move || {
bag2.item_action(item_list2[x].item_attr1, item_list2[x].item_attr2);
})
)
}
for h in handles {
println!("Here");
h.join().unwrap();
}
}
When running, I only got one line, and the program just stops there without returning.
Item attributes 0 and 1 are in Bag attribute list!
May I know what went wrong? Please see code in Playground
Updated:
With suggestion from #loganfsmyth, the program can return now... but still only prints 1 line as above. I expect it to print 10 because my item_list has 10 items. Not sure if my thread logic is correct.
I have added println!("Here"); when calling join all threads. And I can see Here is printed 10 times, just not the actual log from item_action
I believe this is because Rust is not running your
if self.attributes.lock().unwrap().contains(&item_attr1) ||
self.attributes.lock().unwrap().contains(&item_attr2) {
expression in the order you expect. The evaluation order of subexpressions in Rust is currently undefined. What appears to be happening is that you essentially end up with
const condition = {
let lock1 = self.attributes.lock().unwrap();
let lock2 = self.attributes.lock().unwrap();
lock1.contains(&item_attr1) || lock2.contains(&item_attr2)
};
if condition {
which is causing your code to deadlock.
You should instead write:
let attributes = self.attributes.lock().unwrap();
if attributes.contains(&item_attr1) ||
attributes.contains(&item_attr2) {
so that there is only one lock.
Your code would also work as-is if you used an RwLock or ReentrantMutex instead of a Mutex since those allow the same thread to have multiple immutable references to the data.
I have a struct with two Vecs wrapped in RefCells. I want to have a method on that struct that combines the two vectors and returns them as a new RefCell or RefMut:
use std::cell::{RefCell, RefMut};
struct World {
positions: RefCell<Vec<Option<Position>>>,
velocities: RefCell<Vec<Option<Velocity>>>,
}
type Position = i32;
type Velocity = i32;
impl World {
pub fn new() -> World {
World {
positions: RefCell::new(vec![Some(1), None, Some(2)]),
velocities: RefCell::new(vec![None, None, Some(1)]),
}
}
pub fn get_pos_vel(&self) -> RefMut<Vec<(Position, Velocity)>> {
let mut poses = self.positions.borrow_mut();
let mut vels = self.velocities.borrow_mut();
poses
.iter_mut()
.zip(vels.iter_mut())
.filter(|(e1, e2)| e1.is_some() && e2.is_some())
.map(|(e1, e2)| (e1.unwrap(), e2.unwrap()))
.for_each(|elem| println!("{:?}", elem));
}
}
fn main() {
let world = World::new();
world.get_pos_vel();
}
How would I return the zipped contents of the vectors as a new RefCell? Is that possible?
I know there is RefMut::map() and I tried to nest two calls to map, but didn't succeed with that.
You want to be able to modify the positions and velocities. If these have to be stored in two separate RefCells, what about side-stepping the problem and using a callback to do the modification?
use std::cell::RefCell;
struct World {
positions: RefCell<Vec<Option<Position>>>,
velocities: RefCell<Vec<Option<Velocity>>>,
}
type Position = i32;
type Velocity = i32;
impl World {
pub fn new() -> World {
World {
positions: RefCell::new(vec![Some(1), None, Some(2)]),
velocities: RefCell::new(vec![None, None, Some(1)]),
}
}
pub fn modify_pos_vel<F: FnMut(&mut Position, &mut Velocity)>(&self, mut f: F) {
let mut poses = self.positions.borrow_mut();
let mut vels = self.velocities.borrow_mut();
poses
.iter_mut()
.zip(vels.iter_mut())
.filter_map(|pair| match pair {
(Some(e1), Some(e2)) => Some((e1, e2)),
_ => None,
})
.for_each(|pair| f(pair.0, pair.1))
}
}
fn main() {
let world = World::new();
world.modify_pos_vel(|position, velocity| {
// Some modification goes here, for example:
*position += *velocity;
});
}
If you want to return a new Vec, then you don't need to wrap it in RefMut or RefCell:
Based on your code with filter and map
pub fn get_pos_vel(&self) -> Vec<(Position, Velocity)> {
let mut poses = self.positions.borrow_mut();
let mut vels = self.velocities.borrow_mut();
poses.iter_mut()
.zip(vels.iter_mut())
.filter(|(e1, e2)| e1.is_some() && e2.is_some())
.map(|(e1, e2)| (e1.unwrap(), e2.unwrap()))
.collect()
}
Alternative with filter_map
poses.iter_mut()
.zip(vels.iter_mut())
.filter_map(|pair| match pair {
(Some(e1), Some(e2)) => Some((*e1, *e2)),
_ => None,
})
.collect()
You can wrap it in RefCell with RefCell::new, if you really want to, but I would leave it up to the user of the function to wrap it in whatever they need.
I'm working on a game that involves a bunch of Beetle objects stored in a HashMap. Each beetle has a position, and it can also have a target id which is the key for another beetle in the hash. If a beetle has a target, it needs to move toward the target each time the game loop executes.
I can't perform the lookup of the target's current position, because you can't have a mutable and immutable borrow at the same. I get that, but any ideas how to restructure for my specific case?
I think I'm just getting caught up in how easy this would be in pretty much any other language, I can't see the idiomatic Rust way to do it. Here's a pretty minimal but complete example:
use std::collections::HashMap;
type Beetles = HashMap<i32, Beetle>;
struct Beetle {
x: f32,
y: f32,
target_id: i32,
}
impl Beetle {
fn new() -> Beetle {
Beetle {
x: 0.0,
y: 0.0,
target_id: -1,
}
}
}
fn main() {
let mut beetles: Beetles = HashMap::new();
beetles.insert(0, Beetle::new());
beetles.insert(1, Beetle::new());
set_target(&mut beetles, 0, 1);
move_toward_target(&mut beetles, 0);
}
fn set_target(beetles: &mut Beetles, subject_id: i32, target_id: i32) {
if let Some(subject) = beetles.get_mut(&subject_id) {
subject.target_id = target_id;
}
}
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(subject) = beetles.get_mut(&beetle_id) {
if let Some(target) = beetles.get(&subject.target_id) {
// update subject position to move closer to target...
}
}
}
You could solve your specific problem by performing a double lookup for the subject. First, borrow immutably from the hash map to collect the information necessary for updating the subject. Then finally update the subject using the collected information by borrowing mutably from the hash map:
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(subject_target_id) = beetles.get(&beetle_id).map(|b| b.target_id) {
let mut target_xy = None; // example
if let Some(target) = beetles.get(&subject_target_id) {
// collect information about target relevant for updating subject
target_xy = Some((target.x, target.y)) // example
}
let subject = beetles.get_mut(&beetle_id).unwrap();
// update subject using collected information about target
if let Some((target_x, target_y)) = target_xy{ // example
subject.x = target_x;
subject.y = target_y;
}
}
}
However, it is likely that you will run in similar and more complex problems with your beetles in the future, because the beetles are your central game objects, which you will likely want to reference mutably and immutably at the same time at several places in your code.
Therefore, it makes sense to wrap your beetles in std::cell::RefCells, which check borrow rules dynamically at runtime. This gives you a lot flexibility when referencing beetles in your hash map:
fn main() {
let mut beetles: Beetles = HashMap::new();
beetles.insert(0, RefCell::new(Beetle::new()));
beetles.insert(1, RefCell::new(Beetle::new()));
set_target(&mut beetles, 0, 1);
move_toward_target(&mut beetles, 0);
}
fn set_target(beetles: &mut Beetles, subject_id: i32, target_id: i32) {
if let Some(mut subject) = beetles.get_mut(&subject_id).map(|b| b.borrow_mut()) {
subject.target_id = target_id;
}
}
fn move_toward_target(beetles: &mut Beetles, beetle_id: i32) {
if let Some(mut subject) = beetles.get(&beetle_id).map(|b| b.borrow_mut()) {
if let Some(target) = beetles.get(&subject.target_id).map(|b| b.borrow()) {
//example for updating subject based on target
subject.x = target.x;
subject.y = target.y;
}
}
}
updated Beetles type:
type Beetles = HashMap<i32, RefCell<Beetle>>;