Ogeon/rustful

contribute a todobackend example implementation using rustful

Closed this issue ยท 19 comments

Any chance I could persuade you to build a todobackend implementation using Rustful? It's a pretty simple process and the end result would provide a nice reference implementation for people new to Rustful. You'd also gain a little bit of free marketing by having the implementation listed up on todobackend.com.

I'm happy to help if you have any questions while building out the implementation.

Ogeon commented

That may be a good example, but it will have to be as a standalone example if it should use a database library. But that's not a bad thing. I'll keep this open and look it again tomorrow. This may also be good material for some kind of tutorial if that would be interesting.

I read the example in other language, seems db is not mandatory. I will try to implement it during the weekend.

Ogeon commented

Sure, go ahead ๐Ÿ‘ And feel free to ask anything.

here is my progress.
https://github.com/swuecho/todo-rust/blob/master/src/main.rs

basically, an item struct and a vec of items. operate on items based on the http request.

at first, I follow the hello_world example, but all the handlers need shared items. so I switch to the handler_storage example. but the approach seems verbose, i.e. add an operation then match the operation, generate json based on it.

any suggession? Thanks.

also, my rust code probably is not good in general either.

Ogeon commented

Nice to see that you have started. I'm just about to go to bed, so my head is not as clear as it should, but here are some comments I can come up with off the top of my head:

let id = self.id.to_owned();
format!("{}/{}/{}", host, path, id)

This can be written as format!("{}/{}/{}", host, path, self.id) because self.id will only be borrowed during the formatting. You will then avoid a clone.

operation in TODO would probably be better as an enum, since that would make it a part of the type system and guarantee an exclusive match. Also, ToDo or Todo would probably be the idiomatic formatting of the struct name, but that's just a style detail. Cow (copy on write) and Arc (atomic reference counting) are some examples.

You can also make separate functions for each operation and include them in the handlers. That may be better in this case. You would also have to wrap the items in an Arc to make it sharable and lock it using a Mutex or an RwMutex to make it concurrently mutable.

You can also use the content_type! macro for setting the content type. Downside at the moment is that it does string parsing.

I may have missed some points and I will probably be able to give better comments tomorrow, so bear with me. I hope this was somewhat helpful.

thanks for the suggestions!

for the id.to_owned() , to be honest, I am not sure what I was doing. I guess I was trying my best to make the borrow checker happy.

for the Arc , I will wait it for later. Too much to absorb at one time.

It will be good to make separate function call for each route, but I did not find a way to add a global var (the items) into the handlers. the handler only accept context and response, it will be good if it accept extra argument.

Ogeon commented

There are plans to add some kind of globally accessible storage for situations like this, as can be seen in #25. The solution for now is to add references in the handlers themselves. For example:

struct MyHandler {
    data: Arc<MyData>,
    handler_fn: fn(Context, &MyData, Response)
}

impl Handler for MyHandler {
    fn handle_request(context: Context, response: Respone) {
        self.handler_fn(context, &self.data, response)
    }
}

thanks! I will try this. at least, I avoid another dispatch compared to the operation approach.

it seems that the all the handler in the routes have to be the same type. I implement the status_ok to walkaround it.

also, I find in (self.handler_fn)(&context, &self.items), rust is not smart enough to figure out method call and struct atribute. i.e. the first pair of () can not be removed.

hit another wall when I need to mute the shared items. obviously I have to change the data to be mutable, but then I have to change all of them to mute, even when the data is not required or will not get mutated as in show_all.

Hopefully, this is the last barrier to get it work. thanks for help.

Ogeon commented

The TreeRouter<T> works like any other generic collection (for example Vec<T>), so every "element" has to have the same type. You can put the handlers in a Box and cast them to Box<Handler + Send + Sync> if you want to have any type or use and enum if the difference between the types is small.

Yeah, I forgot about that when I wrote the example.

This is where RwLock is necessary. A Vec is not completely thread safe by itself, so you have to make sure no simultaneous writes and reads happens. RwLock lets you have one write lock or multiple read locks, but not both at the same time. There is also Mutex but it doesn't make a difference between read and write locks.

It looks like you are on the right way ๐Ÿ‘

seems RwLock is better then borrow? you have multiple read and one writer. so the question is why should I use borrow at all? I learn rust mostly for the rust book, which does not talk about Rwlock . the book actually talk about Arc, but does not tell the difference between borrow vs Arc or Rc. Thanks.

I also post similar question about Rc
https://users.rust-lang.org/t/car-wheel-example-in-shared-ownership-section-of-rust-book/1206/1

Ogeon commented

RwLock is only useful for concurrency situations, where multiple threads are reading and writing to the same data. It comes with a runtime overhead because it has to set lock variables and check these when the data is accessed, while a normal borrow is compile time enforced and is completely free. The lock will make threads queue up and wait for their turn if they want to access the data, unless all threads want to read. I would recommend that you read a bit about mutexes for more details.

It's essentially the same situation with Rc and Arc. Both have a built in counter that counts all the references and checks it when the reference is destroyed. This happens at runtime and will make the program slightly slower. Arc has an atomic counter, meaning that it is thread safe, and that comes with some additional complexity. A normal borrow, on the other hand, is once again completely free because it is tracked when the program is compiled and not when it is running.

Ogeon commented

I have added a way to store things globally now, so you don't have to put things in the handlers any more.

Thanks. I will try that. actually, I got the 'post' method work use Arc.

Ogeon commented

I have been dropping in from time to time to have a look at your progress and it looks good so far.

I added a new (actually old) example, in the file examples/post.rs, where you can see how the global field can be used. I expect that you can skip the Arc if you store the posts globally, since it will have only one owner, but the RwLock is still necessary.

Ogeon commented

An implementation of this is going to serve as an example in this repo, but I will not maintain a live server in the near future.

I'm closing this because this new "blessed" implementation and because of general inactivity. Anyone who still feels like they want to maintain a server are completely free to do so. ๐Ÿ˜„ No need to worry or ask me about it.

RoPP commented

FYI, I've made a new repo with all the necessary to deploy a heroku instance and I've made a PR to add it to http://www.todobackend.com.

Ogeon commented

Oh, nice! ๐Ÿ˜„ You may want to add a link to https://crates.io/crates/rustful as a shortcut for anyone who may want to know more.

Hey @RoPP I see your branch here but I don't see a PR for it on the TodoBackend/todo-backend-site repo. If you could submit patch-1 as a PR I'll happily pull it in.