Two connected table-like structs, need advice

I need some general advice on mutable and interconnected structures, and I'm not sure I can test everything in sandbox samples.

TL/DR:

  • I have two table-like. Rows in table 2 reference rows in table 1 with Arc or Arc<Mutex<T>> (not settled on this yet).
  • I need to edit the first table, then propagate updates to the second, and not break calculations on the second one.
  • Web API (Axum) receives
    • calculation requests (read only), on both tables (I may need to index into table 1, then query items in table 2 and work on them).
    • requests to edit table 1

The requests can overlap each other by time, so I should lock the struct for editing.

Two structures are like tables -- they contain rows and an index to query for items via other attributes than ID.

Question is: is the way I write it viable at all? (I know it can be done in a database, but that will also need some tech and intellectual overhead, and I optimize for performance of both queries and edits, and not sure the DB will perform well.)

Here are the example structs:

struct Table1 {
	rows: Vec<Arc<Mutex<Item1>>>,
}

struct Item1 {
	raw_key: usize,
	data: f32
}

struct Table2 {
	rows: Vec<Arc<Mutex<Item2>>>
}

struct Item2 {
	ref_key: usize,
	raw: Arc<Mutex<Item1>>,
	more_data: f32
}

struct AppState {
	raw_table: Arc<Table1>, // mutex?
	refs_table: Arc<Table2>
}

For more details, edits are done via Update trait:

trait Update {
	type UpdateIn;
	type UpdateOut;
	fn update(&mut self, updates: &[Self::UpdateIn]) -> Vec<Self::UpdateOut>;
}

enum UpdateRaw {
	AddItem(usize, f32),
	DeleteItem(usize),
	UpdateItem(usize, f32),
}

impl Update for Table1 {
	type UpdateIn = UpdateRaw;
	type UpdateOut = UpdateRefs;
	fn update(&mut self, updates: &[Self::UpdateIn]) -> Vec<Self::UpdateOut> {
		for u in updates {
			match u {
				UpdateItem(id, data) => {
						let mut item = self.rows.get(id).unwrap().lock();
						item.data = ...;
					},
					, // this should be Result<...>, but for example simplicity it's just unwrap
				...
			}
		}
	}
}

impl Update for Table2 {
	...
}

Working code that will process edits:

    async fn do_query(..., State(state): State<AppState>) -> ... {
    	let mut l1 = state.raw_table.lock();
    	let mut l2 = state.refs_table.lock(); // locking everything right away to avoid criss-cross deadlocks
    	let out_edits = l1.update(&updates);
    	l2.update(&out_edits);
    }
    async fn do_edit(..., State(state): State<AppState>) -> ... {
    	let query_var = ...
    	let data = state.refs_table.query(&query_var);
    }

If I read this code correctly, there's no need for the "inner" mutexes. When you lock raw_table and refs_table, the executing thread has exclusive access to the entire tables.

Your approach is viable, but since the refs table already has an index into the raw table, it really doesn't need the Arc. You could just have two Vecs, with no need for Arc at all, which will be more performant in most cases. Like this:

struct Table1 {
	rows: Vec<Item1>,
}

struct Table2 {
	rows: Vec<Item2>
}

1 Like

This didn't actually work, because individual rows haven't DerefMut. I wonder what to pack them in?

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

struct RawItem(i32);

struct RawTable {
	pub data: Vec<Arc<RawItem>>
}

struct RefinedItem {
    raw: Arc<RawItem>,
    local_data: i32
}

struct RefinedTable {
	raw: Arc<Mutex<RawTable>>,
	refined: Vec<RefinedItem> 
}

struct State {
	refined: Arc<Mutex<RefinedTable>>,
	raw: Arc<Mutex<RawTable>> // should be here to do updates from an API method
}


fn main() {
    let rawitem = Arc::new(RawItem(123));
	let raw_table = Arc::new(Mutex::new(RawTable { data: vec![rawitem.clone()] }));
	let ri = RefinedItem { raw: rawitem, local_data: 321 };
	let refined_table = Arc::new(Mutex::new(RefinedTable { raw: raw_table.clone(), refined: vec![ri] }));
	let st = State { refined: refined_table, raw: raw_table };

	let mut gg = st.raw.lock().unwrap();
	gg.data.push(Arc::new(RawItem(456)));
	
	let mut raw_lock = st.raw.lock().unwrap();
	let mut item_edit = &raw_lock.data[0];

	item_edit.0 = 789;
	println!("item edit {:?}", item_edit.0);
	//gg.data.push(456);
}

Arc and Rc haven't DerefMut:

   |
38 |     item_edit.0 = 789;
   |     ^^^^^^^^^^^^^^^^^ cannot assign
   |
   = help: trait `DerefMut` is required to modify through a dereference, but it is not implemented for `Arc<RawItem>`

For more information about this error, try `rustc --explain E0594`.
warning: `playground` (bin "playground") generated 2 warnings
error: could not compile `playground` (bin "playground") due to 1 previous error; 2 warnings emitted