Live data from Hacker News

Pencil: A Microframework Inspired by Flask for Rust

fengsp.github.io

21–30 of 58 posts

Re: Pencil: A Microframework Inspired by Flask for Rust

#21
post #5

I've found that whenever I start a small web project in a new language, I always look for the "Flask" of that language. I love using a framework that gives the bare essentials and nothing else. I don't use Rust, but your code examples look great! I understand that Rust has less opportunity for "magic" to clean things up (for example, in Python you can use a decorator to specify a route for a function), but otherwise…

There's probably some opportunity to use custom annotations in Rust to achieve the same thing. #[route("/foobar")] fn user(_: &mut Request) -> PencilResult { // ... } However, procedural macros are unstable, but Rust is able to do amazing things with macros.

I know very little Rust, but I was actually looking at how this could be done in Rust some time ago, but I didn't get anything working. All the routes (e.g. /foobar and accompanying function) needs to be collected by the "router" somehow which begs for a global route-container that the route macro (like Flasks route decorator) would need to add routes to, similar to how it is done is Flask, and, well, yeah, I couldn't find a rusty way to do it.

Bearing in mind that macros er unstable, can this be done in Rust, and if so, what would the process be?

Re: Pencil: A Microframework Inspired by Flask for Rust

#22
post #3

Looks good! I don't know how I feel about using OK for anything but 200 responses though.

`Ok` is a member of the Rust `Result` enum: https://doc.rust-lang.org/std/result/enum.Result.html (and `PencilResult` as well)

Since it's integral to error handling in Rust you probably can't get away from it.

Re: Pencil: A Microframework Inspired by Flask for Rust

#23
post #21

Earlier quoted context omitted.

There's probably some opportunity to use custom annotations in Rust to achieve the same thing. #[route("/foobar")] fn user(_: &mut Request) -> PencilResult { // ... } However, procedural macros are unstable, but Rust is able to do amazing things with macros.

I know very little Rust, but I was actually looking at how this could be done in Rust some time ago, but I didn't get anything working. All the routes (e.g. /foobar and accompanying function) needs to be collected by the "router" somehow which begs for a global route-container that the route macro (like Flasks route decorator) would need to add routes to, similar to how it is done is Flask, and, well, yeah, I couldn'…

There are a few different ways one could achieve this. The first options would be to generate a struct:

    struct RouteUser {
        route: &'static str,
        f: fn(_: &mut Request) -> PencilResult
    }
Then given

    #[route("/user")]
    fn user(_: &mut Request) -> PencilResult {}
You would hijack the `user` function to return an instance of the struct.

    fn user() -> RouteUser {
        // The actual function the user wrote:
        fn user(_: &mut Request) -> PencilResult {
            // ...
        }

        RouteUser {
            // Generated from the annotation argument
            route: "/user",
            f: user
        }
    }
There is a difference between item decorators (`#[foobar]`) and regular procedural macros and I'm not completely sure if you could in-fact significantly change the given function. I haven't touched procedural macros in a while.

To use the above route, you would simply have a `Route` trait perhaps.

    trait Route {}
And implement it for each generated struct:

    impl Route for RouteUser {}
Then you could use the route as

    app.route(user());
Which could be defined as

    fn route(r: R) where R: Route {
        // ...
    }

Re: Pencil: A Microframework Inspired by Flask for Rust

#24

Anyone know of any benchmarks? This seems to be built on top of hyper. I remember checking out hyper a couple months ago and being disappointed with its performance. Last I checked it was using synchronous IO, and was performing about an order of magnitude worse than equivalent Go. That could certainly change, but I'm hesitant to use Rust for an HTTP server like I would with Go until I see better performance.

Switching to asynchronous I/O isn't going to magically result in better performance on HTTP workloads. I don't think most of what any performance difference you're seeing is due to that: I suspect instead that it's relatively "boring" optimization work that has yet to be done in Hyper. The primary difference between async and synchronous I/O is (a) better memory usage due to not having a stack per connection; (b) you…

Go's networking library actually does call epoll/kqueue/etc on the backend, so it is using nonblocking io. Using nonblocking IO will provide better performance because it will increase the number of concurrent requests it can serve. It's not a silver bullet, and can be very difficult to implement at the application layer to avoid blocking, but it will give markedly better performance.

Re: Pencil: A Microframework Inspired by Flask for Rust

#25
post #5

I've found that whenever I start a small web project in a new language, I always look for the "Flask" of that language. I love using a framework that gives the bare essentials and nothing else. I don't use Rust, but your code examples look great! I understand that Rust has less opportunity for "magic" to clean things up (for example, in Python you can use a decorator to specify a route for a function), but otherwise…

>I always look for the "Flask" of that language.

I always look for the "Sinatra" of that language.

Re: Pencil: A Microframework Inspired by Flask for Rust

#26
post #14

This requires less boilerplate than Iron (which is built upon Hyper). I'll definitely be checking this out. That said, I've been very happy with Iron thus far.

Although Iron is powerful, I find there is a lot of complexity around it. The middleware concept mostly contributes to this in addition to the abstractions over Hyper constructs.

Re: Pencil: A Microframework Inspired by Flask for Rust

#27
post #16

Great start. Instead of numeric codes, perhaps use an enum for all the acceptable status codes? As Jane Street Says, make illegal state unrepresentable.

This is a good slogan, but there's some subtleties when it comes to HTTP status codes. That is, there are the defined ones, but any number is legit, so you end up with an enum with a member that's basically "anything we don't know about", so it's not as clear-cut as it is in other situations.

Re: Pencil: A Microframework Inspired by Flask for Rust

#29
post #5

I've found that whenever I start a small web project in a new language, I always look for the "Flask" of that language. I love using a framework that gives the bare essentials and nothing else. I don't use Rust, but your code examples look great! I understand that Rust has less opportunity for "magic" to clean things up (for example, in Python you can use a decorator to specify a route for a function), but otherwise…

There is a language plugin for pythonic decorators in Rust:

https://github.com/Manishearth/rust-adorn

Re: Pencil: A Microframework Inspired by Flask for Rust

#30

Earlier quoted context omitted.

Switching to asynchronous I/O isn't going to magically result in better performance on HTTP workloads. I don't think most of what any performance difference you're seeing is due to that: I suspect instead that it's relatively "boring" optimization work that has yet to be done in Hyper. The primary difference between async and synchronous I/O is (a) better memory usage due to not having a stack per connection; (b) you…

Go's networking library actually does call epoll/kqueue/etc on the backend, so it is using nonblocking io. Using nonblocking IO will provide better performance because it will increase the number of concurrent requests it can serve. It's not a silver bullet, and can be very difficult to implement at the application layer to avoid blocking, but it will give markedly better performance.

But one of the reasons you're able to process more with async I/O is bypassing the costs of the thread-per-connection model. So you're not forced to store all the thread's stacks and you don't have expensive context switches.

As the parent illustrated, even with Go using nonblocking I/O, it's perceived benefits in that area isn't that great because Go still semantically has a thread-per-connection. So the performance characteristics of Go isn't simply async vs sync I/O.

Post reply on HN