
Pencil: A Microframework Inspired by Flask for Rust - fsp
https://fengsp.github.io/blog/2016/3/introducing-pencil/
======
barosl
Copying my comment on this from Reddit:

Great, I'll definitely have to try this! I've used several Rust web
frameworks, including Iron[1], Nickel[2], and Rustful[3]. Each of them has
their own strengths and weaknesses, so I've been launching new web sites using
all of them to see what fits me best. (Until now, Rustful felt the most
comfortable. But I like the others too!) Now I have one more thing to
evaluate!

Before diving into this, one thing that caught me at a glance is the order of
the arguments in the routing rules. It is using `/user/<int:user_id>`, but I
think `/user/<user_id:int>` is more natural. A small difference, but I believe
this is one thing that Bottle[4] did right. When types come later, it is
easier to omit them if there's a default (e.g. `<username>` has the same
meaning as `<username:str>`) and it is also consistent with the existing type
annotation syntax of Rust. I suspect Flask uses the current order only because
of the backward compatibility concerns. But only mitsuhiko can tell for sure.

Also, it would be great if `ViewFunc` is extended to accept the functions
other than the ones returning `Result<Response, _>`, e.g. `Result<String, _>`
and `Result<Vec<u8>, _>`. It is true that there exist the `From`
implementations in `Response`, but it is generally more convenient to return
`String` directly from the handlers rather than manually forming
`Response::from("...")`. I think I can prepare a PR if you like!

Thanks again for another attempt to use Rust as a web language!

[1] [http://ironframework.io/](http://ironframework.io/)

[2] [http://nickel.rs/](http://nickel.rs/)

[3] [https://github.com/Ogeon/rustful](https://github.com/Ogeon/rustful)

[4] [http://bottlepy.org/](http://bottlepy.org/)

~~~
kibwen
For those reading, note that mitsuhiko did end up responding to this on
Reddit:
[https://www.reddit.com/r/rust/comments/49jv81/introducing_pe...](https://www.reddit.com/r/rust/comments/49jv81/introducing_pencil_a_microframework_inspired_by/d0sku9f)

------
jdreaver
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 it
looks very clear. Awesome work!

~~~
TheHydroImpulse
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.

~~~
fredsir
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?

~~~
TheHydroImpulse
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: R) where R: Route {
            // ...
        }

~~~
fredsir
That seems like a good approach, although one thing is missing: when you use
the route-decorator in Flask, the route is registered to the app object "via
magic", whereas in your example, you still have to register it manually
(`app.route(user())`). Now, I am partial to liking registering manually, like
in your example and in Pencil itself, since that's clearer than having a lot
of routes spread around in a whole lot of files that are magically registered,
but just for the kicks, would it be possible to not having to manually
register the route to the app object?

------
minionslave
Probably a good idea to add "web development framework" somewhere on the page.
It'll not only improve your Search engine optimization. But it also will be
more explicit for the visitors.

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

~~~
pcwalton
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 can avoid the overhead
of context switches to wake up an I/O thread; (c) thread spawning performance
is faster due to the kernel not having to be involved. Golang's advantages in
(a) and (b) are _much_ less than what is typically thought of as "async I/O",
because it still semantically uses a thread-per-connection and so is
performing the same operations that a synchronous I/O implementation performs,
just with a different implementation strategy.

~~~
openasocket
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.

~~~
TheHydroImpulse
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.

~~~
openasocket
>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.

One reason, not the only, or the most important. The primary advantage of non
blocking IO is that the CPU isn't sitting idle while waiting for an IO
operation to complete. We aren't wasting an entire core to write the response.

~~~
pcwalton
That's not how blocking I/O works! Sitting in a busy loop on the CPU burning
power polling the I/O device was, like, how the Apple II may have worked, but
it hasn't worked like that on any major OS since at least 1990. Operating
systems perform context switches on I/O.

~~~
f2f
> Operating systems perform context switches on I/O.

which are very cheap :)

~~~
pcwalton
And if you accept that (which I'm not sure I do, but anyhow) then you accept
that there isn't much difference between userspace M:N and blocking 1:1
threading.

------
maxcan
Great start. Instead of numeric codes, perhaps use an enum for all the
acceptable status codes?

As Jane Street Says, make illegal state unrepresentable.

~~~
steveklabnik
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.

~~~
maxcan
Good point, this is where Rust's flexible enums are an advantage relative to
C-style. If you'll forgive the Haskell syntax, you can have something like:

    
    
        data StatusCoode
          = Ok200
          | Missing404
          | Error500
          | OtherStatusCode Int
    

To rephrase the slogan a bit - make an illegal state a bit more difficult to
represent or at least the risk of an illegal state clear to the programmer.

~~~
steveklabnik
Exactly:
[https://github.com/hyperium/hyper/blob/master/src/status.rs#...](https://github.com/hyperium/hyper/blob/master/src/status.rs#L31-L221)

~~~
maxcan
My bad.. I was just basing my comment off the readme. In general, I love what
you guys are doing and my team is looking closely at Rust and Leaf.

~~~
steveklabnik
No worries! I just meant to confirm your suspicions :)

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

~~~
jimktrains2
Well, 100- and 300- class respones aren't errors, but yeah, 400- and 500-
might make more sense as Err.

You could also look at it from another point-of-view. Ok() means that I am
able to fully handle this request, even if it's an HTTP error code, and Err
being "nope, don't know. Sorry bud".

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

~~~
squiguy7
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.

------
nickpsecurity
That looks _so simple_. Even simple enough for embedded systems. I like it!
Maybe I need to look at Flask, too.

~~~
untothebreach
Flask is my favorite tool for writing web apps in python. Django is nice, but
I like the idea of building a framework up around the application, instead of
shoehorning an application into a framework, and Flask lets you only bring in
the parts you need.

~~~
nickpsecurity
That's exactly my thought. Also helps with portability and long-term
maintenance.

------
yeukhon
[https://github.com/fengsp/pencil/blob/master/examples/hello/...](https://github.com/fengsp/pencil/blob/master/examples/hello/src/main.rs#L84)

    
    
        app.get("/", "hello", hello);
        app.get("/user/<int:user_id>", "user", user);
        ....
    

This reminds me of Pyramid more than Flask though, just my personal
observation. The code looks pretty readable even from a non-Rust programmer.

------
gragas
This is great! It looks like I'll begin my Rust journey earlier than expected.

------
gravypod
This look like some amazing work.

I would love to see how Rust will influence the web development sphere and I'd
like to see if it would cut down on some common bugs that are made in
production web development.

------
fiatjaf
The magic of Flask is that that thing is full of state and globals you just
import and they work, like request, app, session.

~~~
Svenstaro
That is also, I think, one of flask's biggest weaknesses. It does make
everything simpler though. I'm just not very fond of magic globals. It also
doesn't fit very well into REST and HTTP. On the other hand, I love flask for
just about everything else.

------
bliti
Very nice and readable. What are the plans for this? Anyone share to comment
if there is a development roadmap or if its just an exercise in programming.

~~~
Zikes
[https://github.com/fengsp/pencil/graphs/contributors](https://github.com/fengsp/pencil/graphs/contributors)

It looks like it's a bit of a pet project at the moment, but these sorts of
things have a habit of taking off in a big way sometimes. Probably a big "it
depends".

~~~
bliti
It does look simpler than using Go for a similar type of project, IMO.

------
jonesb6
Missed opportunity, should've called it:

"The Blueprint²: The Gift & the Curse"

------
julie1
isn't flask inspired by a WSGI framework in ruby originally? Like Sinatra?

Hum, yes, that is what the author says :)

------
viperscape
That's a strange license to use

~~~
timClicks
CC is fine for docs.

