Multithreaded REST API

I am new to rust and still thinking in C++ terms (I think)... I have a repo https://github.com/CaribouJohn/todo-api which will contain a simple REST API for Todos Tasks etc. The topic of the code isn't important but the code structure and how I get it to work. I have already learned quite a lot in getting it to the stage it is currently at : See the readme or todo-api | This code is my learning of Rust and it’s intricacies - I wanted to do a fairly well defined easy project that allowed me to create something nearer a real project. It is likely to contain some front end (Angular) before it is done. for the notes I am making while trying things out.

Q. I want to ask how I can structure this so that the struct TaskList does more of the work and the Rocket handlers for the API requests can deal with the requests....

outline of code (See repo for actual)

type TaskHashMap = HashMap<u32,Task>;

pub struct TaskList  {
	pub tasks :  Arc<RwLock<TaskHashMap>>,
}

impl  TaskList {
	pub fn new() -> TaskList {
		TaskList {
			tasks : Arc::new(RwLock::new(TaskHashMap::new())),
		}
	}
	...
}

//in main functions

#[post("/tasks", data = "<t>")]
fn add_task(state: State<tasks::TaskList>, t: Json<Task>) -> String {
	let tl =  self.tasks.clone();
	let mut my_tasks = tl.read().expect("RWLOCK poisoned");	

    //we should get the request body here and read in the json body
    let o = tasks::Task::new(...);

    //insert the object 
    let r = match my_tasks.insert(new_id, o) {
		...
    };
	...
}

fn main() {
    let task_db= tasks::TaskList::new();
 
    rocket::ignite()
        .mount("/", routes![get_tasks,add_task,get_task_by_id])
        .manage(task_db)
        .launch();
}

Basically I have to do all the work in the handlers, and I wanted to have methods implemented for the struct that would do the relevent operations (lock correctly, return a vec of elements etc... add a new one to the list etc....)

Am I thinking too much in a C++ way - I feel like I have missed something fundemental.

E.G. I tried removing the Arc as the state in Rocker seems to be thread safe but :

impl  TaskList {
...
	pub fn getAll(self) -> String{
		let my_tasks = self.tasks.read().expect("Should have allowed read");
		let my_vec: Vec<&Task> = my_tasks.iter().map(|(_,v)| v.clone()).collect();
		let s = serde_json::to_string(&my_vec).unwrap();
		return s.clone();	
	}

}

When I call it from the handler

error[E0507]: cannot move out of dereference of `rocket::State<'_, tasks::TaskList>`
  --> src/main.rs:15:5
   |
15 |     state.getAll()
   |     ^^^^^ move occurs because value has type `tasks::TaskList`, which does not implement the `Copy` trait

TIA
--John

You're not showing the full code, but I suspect the problem is getAll(self) wants to take exclusive ownership of entire TaskList (i.e. have the right to destroy it), but you have &TaskList or &mut TaskList, which is a temporary borrow (you will have to give it back, you can't destroy it).

Make getAll take a shared borrow instead, getAll(&self)

1 Like

Thanks,

You were right it wasn't the code in full - I was attempting to give the feel for what I was doing because I wanted to see what people thought about the approach rather than the specifics...

Yes that does fix that particular error , so...

If I have understood this the function signature will determine if the function takes ownership or borrows - rather than the struct being a particular type (pointer/value). I think I need to write some more simple code to get this in my head

thanks for the reply...

You've mentioned pointer/value — beware! Rust thinks in ownership (owned/borrowed), not in pointer/value concepts. They're only superficially similar, and don't translate well. The C/C++ pointer-oriented mindset of "avoid copying" translates poorly to Rust ownership and results in poor choice of Rust types.

Rust has single ownership and no copy constructors, so you basically don't have to worry about copying anything by accident.

Rust's syntax also doesn't make it clear what is a pointer, and what is "by value". For example Box<Foo> compiles to a basic pointer in Rust ((Foo*)malloc()), even though it doesn't have any pointer-like sigil in the type name. OTOH &str is a struct {data, length} passed by value.

2 Likes

That's really interesting and just the kind of response I was looking for. I am worrying about copying and trying to look at this from a "pass by value" , "pass by ref" point of view.

I definitely need to do more lower level examples for myself. I started with the rustlings project fixing the examples (Not finished yet) but I hadn't quite grasped all the concepts...

Great, thanks for that - I will write some simpler examples and work through the basics for myself.

--John

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.