How can I get/store span duration with Rust tracing? - rust

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).

Related

Handling lifetimes when decoding generic types using SQLx

I am attempting to use a generic method to retrieve values from database tables that are structurally identical, but have different types for one of their columns. Simplified example below
async fn query<'a, 'r, T: DatabaseType<Item=T> + Decode<'r, Sqlite> + Type<Sqlite>>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> {
let mut connection = self.pool.acquire().await?;
let mut rows = sqlx::query("Select id, value from table where name = $1")
.bind(name)
.fetch(&mut connection);
let mut results = Vec::new();
while let Some(row) = rows.try_next().await? {
results.push(NamedValue {
name,
value: row.try_get("value")?
})
}
Ok(results)
}
This will not compile, with the error: borrowed value does not live long enough, argument requires that 'row' is borrowed for 'r. The lifetime sqlx::Decode wants ('r), has to be declared as part of the query function's signature, but the resource the lifetime refers to does not exist yet, and only exists when the query executes and the stream is iterated over. I can't omit this bound on the generic, because the type does need to be decodable for try_get to work, so how do I tell the compiler that it is actually completely safe, and that the decoding is happening against a row that will definitely live longe enough for the try_get? Once the value is decoded, it will always have a static lifetime.
Rust playground doesn't include SQLx, an example that can be compiled at home is below:
[package]
name = "sqlx-minimal-example"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1", features = ["full"] }
sqlx = { version = "0.6", features = ["runtime-tokio-rustls", "sqlite"] }
anyhow = "1.0"
futures = "0.3"
And the full application would be:
use anyhow::Result;
use sqlx::{Decode, Row, Sqlite, SqlitePool, Type};
use futures::TryStreamExt;
#[tokio::main]
async fn main() -> Result<()> {
println!("Hello, world!");
Ok(())
}
struct NamedValue<'a ,T> {
name: &'a str,
value: T
}
struct SqliteBackend {
pool: SqlitePool
}
trait DatabaseType {
type Item;
}
impl DatabaseType for f32 {
type Item = f32;
}
impl DatabaseType for i32 {
type Item = i32;
}
impl SqliteBackend {
async fn query<'a, 'r, T: DatabaseType<Item=T> + Decode<'r, Sqlite> + Type<Sqlite>>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> {
let mut connection = self.pool.acquire().await?;
let mut rows = sqlx::query("Select id, value from table where name = $1")
.bind(name)
.fetch(&mut connection);
let mut results = Vec::new();
while let Some(row) = rows.try_next().await? {
results.push(NamedValue {
name,
value: row.try_get("value")?
})
}
Ok(results)
}
}
Higher-ranked trait bounds were the answer. This tells the compiler the type is decodable for all possible lifetimes.
Working function below:
async fn query<'a, T: DatabaseType<Item=T> + for<'r> Decode<'r, Sqlite> + Type<Sqlite>>(&self, name: &'a str) -> Result<Vec<NamedValue<'a, T>>> {
let mut connection = self.pool.acquire().await?;
let mut rows = sqlx::query("Select id, value from table where name = $1")
.bind(name)
.fetch(&mut connection);
let mut results = Vec::new();
while let Some(row) = rows.try_next().await? {
results.push(NamedValue {
name,
value: row.try_get("value")?
})
}
Ok(results)
}

How can I search for another entity inside a Specs system?

I've been playing around with Rust by following along with Roguelike Tutorial, and have started to branch out a bit in hopes of creating some kind of nature simulation.
In my simple POC, I'm trying to have multiple "Creatures" wandering the map looking for entities with the ProvidesHealth component (so like plants or bushes or something that get eaten).
In the Roguelike tutorial, monsters can easily locate the player at all times because the player is shared throughout the world as a resource, but in my case, I can't figure out the best way to simulate this behavior in my Specs system.
The Creature entities have a Viewshed component to act as their vision. I originally thought I'd be able to iterate thru the Viewshed's visible_tiles and check if an entity with the ProvidesHealth entity was there, but I wasn't able to get that work.
Any thoughts on this would be greatly appreciated! I'm not sure if my approach is totally off, or I'm missing something simple.
Thanks!
// [dependencies]
// bracket-lib = { git = "https://github.com/thebracket/bracket-lib.git", rev = "927d229" }
// specs = "0.16.1"
// specs-derive = "0.4.1"
use bracket_lib::prelude::*;
use specs::prelude::*;
use specs_derive::*;
use std::{thread, time};
#[derive(Component)]
struct Position {
x: i32,
y: i32,
}
#[derive(Component)]
struct Renderable {
glyph: FontCharType,
fg: RGB,
bg: RGB,
}
#[derive(Component)]
struct Creature {}
#[derive(Component)]
struct ProvidesHealth {
pub hp_gain: i32
}
#[derive(Component)]
pub struct Viewshed {
pub visible_tiles: Vec<Point>,
pub range: i32,
pub dirty: bool
}
struct State {
ecs: World
}
impl State {
fn run_systems(&mut self) {
let mut vis = VisSystem {};
vis.run_now(&self.ecs);
let mut ai = CreatureAI {};
ai.run_now(&self.ecs);
self.ecs.maintain();
}
}
impl GameState for State {
fn tick(&mut self, ctx: &mut BTerm) {
ctx.cls();
self.run_systems();
// map defined in separate file, but isn't really
// important for this question
// draw_map(&self.ecs, ctx);
let positions = self.ecs.read_storage::<Position>();
let renderables = self.ecs.read_storage::<Renderable>();
for (pos, ren) in (&positions, &renderables).join() {
ctx.set(pos.x, pos.y, ren.fg, ren.bg, ren.glyph);
}
let sleep = time::Duration::from_millis(200);
thread::sleep(sleep);
}
}
fn main() -> BError {
let mut context = BTermBuilder::simple80x50()
.build()?;
let mut gs = State {
ecs: World::new(),
};
gs.ecs.register::<Position>();
gs.ecs.register::<Renderable>();
gs.ecs.register::<Creature>();
gs.ecs.register::<ProvidesHealth>();
gs.ecs.register::<Viewshed>();
// add one Creature
gs.ecs
.create_entity()
.with(Position {x: 10, y: 20})
.with(Renderable {
glyph: to_cp437('#'),
fg: RGB::named(WHITE),
bg: RGB::named(BLACK)
})
.with(Creature {})
.with(Viewshed { visible_tiles : Vec::new(), range: 6, dirty: true })
.with(HealthStats { max_hp: 100, hp: 100 })
.build();
// add one "food" item
gs.ecs
.create_entity()
.with(Position {x: 35, y: 35})
.with(Renderable {
glyph: to_cp437('*'),
fg: RGB::named(WHITE),
bg: RGB::named(BLACK),
})
.with(ProvidesHealth { hp_gain: 10 })
.build();
// map defined in separate file, but isn't really
// important for this question
let mut map = Map::new_map();
gs.ecs.insert(map);
main_loop(context, gs)
}
struct VisSystem {}
impl<'a> System<'a> for VisSystem {
type SystemData = (
WriteExpect<'a, Map>,
Entities<'a>,
WriteStorage<'a, Viewshed>,
ReadStorage<'a, Position>,
);
fn run(&mut self, data: Self::SystemData) {
let (map, entities, mut viewshed, pos) = data;
for (_ent, viewshed, pos) in (&entities, &mut viewshed, &pos).join() {
if viewshed.dirty {
viewshed.visible_tiles = field_of_view(
Point::new(pos.x, pos.y),
viewshed.range,
&*map,
)
}
}
}
}
struct CreatureAI {}
impl<'a> System<'a> for CreatureAI {
#[allow(clippy::type_complexity)]
type SystemData = (
// ...
);
fn run(&mut self, data: Self::SystemData) {
// ... not sure what to do here\
//
// by doing a join on (viewshed, position),
// i'd be able to iterate thru viewshed.visible_tiles,
// but i can't figure out how I could check if a given
// entity located at the Point has the "ProvidesHealth"
// component or not
}
}

How to use actix field stream by two consumers?

I have an actix web service and would like to parse the contents of a multipart field while streaming with async-gcode and in addition store the contents e.g. in a database.
However, I have no clue how to feed in the stream to the Parser and at the same time collect the bytes into a Vec<u8> or a String.
The first problem I face is that field is a stream of actix::web::Bytes and not of u8.
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
while let Some(chunk) = field.next().await {
contents.append(&mut chunk.unwrap().to_vec());
// already parse the contents
// and additionally store contents somewhere
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Any hint or suggestion is very much appreciated.
One of the options is to wrap field in a struct and implement Stream trait for it.
use actix_web::{HttpRequest, HttpResponse, Error};
use futures_util::stream::Stream;
use std::pin::Pin;
use actix_multipart::{Multipart, Field};
use futures::stream::{self, StreamExt};
use futures_util::TryStreamExt;
use std::task::{Context, Poll};
use async_gcode::{Parser, Error as PError};
use bytes::BytesMut;
use std::cell::RefCell;
pub struct Wrapper {
field: Field,
buffer: RefCell<BytesMut>,
index: usize,
}
impl Wrapper {
pub fn new(field: Field, buffer: RefCell<BytesMut>) -> Self {
buffer.borrow_mut().truncate(0);
Wrapper {
field,
buffer,
index: 0
}
}
}
impl Stream for Wrapper {
type Item = Result<u8, PError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<u8, PError>>> {
if self.index == self.buffer.borrow().len() {
match Pin::new(&mut self.field).poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => self.buffer.get_mut().extend_from_slice(&chunk),
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Ready(Some(Err(_))) => return Poll::Ready(Some(Err(PError::BadNumberFormat/* ??? */)))
};
} else {
let b = self.buffer.borrow()[self.index];
self.index += 1;
return Poll::Ready(Some(Ok(b)));
}
Poll::Ready(None)
}
}
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
while let Ok(Some(field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let mut contents: RefCell<BytesMut> = RefCell::new(BytesMut::new());
let mut w = Wrapper::new(field, contents.clone());
let mut p = Parser::new(w);
while let Some(res) = p.next().await {
// Do something with results
};
// Do something with the buffer
let a = contents.get_mut()[0];
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Copying the Bytes from the Field won't be necessary when
Bytes::try_unsplit will be implemented. (https://github.com/tokio-rs/bytes/issues/287)
The answer from dmitryvm (thanks for your effort) showed me that there are actually two problems. At first, flatten the Bytes into u8's and, secondly, to "split" the stream into a buffer for later storage and the async-gcode parser.
This shows how I solved it:
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let field_stream = field
.map_err(|_| async_gcode::Error::BadNumberFormat) // Translate error
.map_ok(|y| { // Translate Bytes into stream with Vec<u8>
contents.extend_from_slice(&y); // Copy and store for later usage
stream::iter(y).map(Result::<_, async_gcode::Error>::Ok)
})
.try_flatten(); // Flatten the streams of u8's
let mut parser = Parser::new(field_stream);
while let Some(gcode) = parser.next().await {
// Process result from parser
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}

How do I read CSV data without knowing the structure at compile time?

I'm pretty new to Rust and trying to implement some kind of database. Users should create tables by giving a table name, a vector of column names and a vector of column types (realized over an enum). Filling tables should be done by specifying csv files. However, this requires the structure of the table rows to be specified at compile time, like shown in the basic example:
#[derive(Debug, Deserialize, Eq, PartialEq)]
struct Row {
key: u32,
name: String,
comment: String
}
use std::error::Error;
use csv::ReaderBuilder;
use serde::Deserialize;
use std::fs;
fn read_from_file(path: &str) -> Result<(), Box<dyn Error>> {
let data = fs::read_to_string(path).expect("Unable to read file");
let mut rdr = ReaderBuilder::new()
.has_headers(false)
.delimiter(b'|')
.from_reader(data.as_bytes());
let mut iter = rdr.deserialize();
if let Some(result) = iter.next() {
let record:Row = result?;
println!("{:?}", record);
Ok(())
} else {
Err(From::from("expected at least one record but got none"))
}
}
Is there a possibility to use the generic table information instead of the "Row"-struct to cast the results from the deserialization? Is it possible to simply allocate memory according to the combined sizes of the column types and parse the records in? I would do something like this in C...
Is there a possibility to use the generic table information instead of the "Row"-struct to cast the results from the deserialization?
All generics replaced with concrete types at compile time. If you do not know types you will need in runtime, "generics" is not what you need.
Is it possible to simply allocate memory according to the combined sizes of the column types and parse the records in? I would do something like this in C...
I suggest using Box<dyn Any> instead, to be able to store reference of any type and, still, know what type it is.
Maintenance cost for this approach is pretty high. You have to manage each possible value type everywhere you want to use a cell's value. On the other hand, you do not need to parse value each time, just make some type checks in runtime.
I have used std::any::TypeId to identify type, but it can not be used in match expressions. You can consider using custom enum as type identifier.
use std::any::{Any, TypeId};
use std::io::Read;
use csv::Reader;
#[derive(Default)]
struct Table {
name: String,
headers: Vec<(String, TypeId)>,
data: Vec<Vec<Box<dyn Any>>>,
}
impl Table {
fn add_header(&mut self, header: String, _type: TypeId) {
self.headers.push((header, _type));
}
fn populate_data<R: Read>(
&mut self,
rdr: &mut Reader<R>,
) -> Result<(), Box<dyn std::error::Error>> {
for record in rdr.records() {
let record = record?;
let mut row: Vec<Box<dyn Any>> = vec![];
for (&(_, type_id), value) in self.headers.iter().zip(record.iter()) {
if type_id == TypeId::of::<u32>() {
row.push(Box::new(value.parse::<u32>()?));
} else if type_id == TypeId::of::<String>() {
row.push(Box::new(value.to_owned()));
}
}
self.data.push(row);
}
Ok(())
}
}
impl std::fmt::Display for Table {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Table: {}", self.name)?;
for (name, _) in self.headers.iter() {
write!(f, "{}, ", name)?;
}
writeln!(f)?;
for row in self.data.iter() {
for cell in row.iter() {
if let Some(&value) = cell.downcast_ref::<u32>() {
write!(f, "{}, ", value)?;
} else if let Some(value) = cell.downcast_ref::<String>() {
write!(f, "{}, ", value)?;
}
}
writeln!(f)?;
}
Ok(())
}
}
fn main() {
let mut table: Table = Default::default();
table.name = "Foo".to_owned();
table.add_header("key".to_owned(), TypeId::of::<u32>());
table.add_header("name".to_owned(), TypeId::of::<String>());
table.add_header("comment".to_owned(), TypeId::of::<String>());
let data = "\
key,name,comment
1,foo,foo comment
2,bar,bar comment
";
let mut rdr = Reader::from_reader(data.as_bytes());
table.populate_data(&mut rdr).unwrap();
print!("{}", table);
}

Access another element immutably while mutating an element of a HashMap

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>>;

Resources