top | item 16689108

Fast, Safe, and Complete(ish) Web Service in Rust

359 points| henridf | 8 years ago |brandur.org | reply

122 comments

order
[+] masklinn|8 years ago|reply
Really nice introductory article, though some of the more… axiomatic assertions, are iffy.

> If it’s possible to build more reliable systems with programming languages with stricter constraints, what about languages with the strongest constraints? I’ve skewed all the way to the far end of the spectrum and have been building a web service in Rust

Bit overselling it, that introduction would work better for something like ATS or possibly Idris. Rust has its strictness, but it's not exactly "you must prove this recursion terminates"-strict

> Synchronous operations aren’t as fast as a purely asynchronous approach

This is misleading. Synchronous sequences are generally faster than asynchronous ones because yielding and getting scheduled back has a non-zero overhead (this is regularly re-discovered, most recently by the developers of "better-sqlite": https://news.ycombinator.com/item?id=16616374).

Async operations are a way to increase concurrency when your synchronous sequence would just be waiting with its thumb up its ass (e.g. on a network buffer filling or on a file read completing), so you can provide more service over the same wallclock time by (hopefully) always using CPU time.

[+] nordsieck|8 years ago|reply
> Synchronous sequences are generally faster than asynchronous ones because yielding and getting scheduled back has a non-zero overhead

Indeed. I remember being shocked when I first read "Thousands of Threads and Blocking I/O" [0] by Paul Tyma.

After reading the C10k problem [1], I just believed that a big pile of threads would never work and never updated my beliefs. While such an approach is still quite memory limited compared to asynchronous approaches, it scales quite far and can have a very high work rate.

[0] https://www.slideshare.net/e456/tyma-paulmultithreaded1

[1] http://www.kegel.com/c10k.html

[+] nicoburns|8 years ago|reply
In other words, for data-bound processing (such as most web services), async approaches are faster (specifically they have greater throughput).
[+] vvanders|8 years ago|reply
> This is misleading. Synchronous sequences are generally faster than asynchronous ones because yielding and getting scheduled back has a non-zero overhead

To get a bit pedantic, synchronization introduces overhead. If you've got an 8-core processor running things async/parallel may complete faster since you aren't pinned to one core.

That said, it's much harder to get right since you have to deal not only with thread sync overhead but also cache/pipeline sharing and a bunch of other gnarly things.

[+] zzzcpan|8 years ago|reply
No. Implicitly yielding and getting scheduled back is exactly what synchronous sequence is. And it cannot ever be faster than asynchronous, because asynchronous doesn't yield. Any "rediscoveries" are just mistakes due to lack of fundamental understanding of these things.
[+] Thaxll|8 years ago|reply
Why would you want to do that in Rust instead of Go / Java / C# since the performance is the same. When I look at code example in the article the syntax is just awful seriously, who want to write web services with that kind of syntax:

time_helpers::log_timed(&log.new(o!("step" => "upsert_episodes")), |_log| { }

It's just non-sense:

impl<S: server::State> actix_web::middleware::Middleware<S> for Middleware {

        fn start(&self, req: &mut HttpRequest<S>) -> actix_web::Result<Started> {

            let log = req.state().log().clone();
            req.extensions().insert(Extension(log));
            Ok(Started::Done)
        }
Rust is a good language but I don't see it any time soon in the web space with that syntax and the lack of mature libraries, plus the time it takes to get anything done. Where I see it is more for super optimized things like serialization and the like as libraries but not as a main program.
[+] staticassertion|8 years ago|reply
Nonsense to you is wonderful to me. I don't think "I am unfamiliar with this syntax" is really a useful observation - Erlang has an extremely unfamiliar syntax, but I don't think that makes it less suitable for building a service.

To answer your question more directly (though I feel the article covers these points), as someone building services in rust, there are a few reason I'd choose Rust over the languages you listed:

a) Largely deterministic performance

Having written services professionally in Java I have seen performance scale linearly with connections, until one day it suddenly spikes massively - why? It was that we crossed some threshold where the GC was suddenly having to do a ton more work, which caused a serious feedback loop - server has to pause, clients don't handle it well, clients hammer server more, server has to pause, etc.

You can have similar issues in Rust but they should be easier to spot (they should be less blow-up-y).

b) Correctness/ Expressiveness (is this a word?)

I find it far, far easier to write correct, expressive code in Rust. For example, I think anyone who has moved from Java 6 to 7 or 8 will admit that Optional is awesome - because it is. In Rust the pattern exposed by Optional is first class, and extends far beyond nullability. This is just one example though.

I find it extremely easy, by comparison to other languages, to write Rust code in a 'type driven' way, and in my experience this is a huge part of (me) writing code with fewer defects.

As the linked article puts it:

> Constraints like a compiler and a discerning type system are tools that help us to find and think about those edges.

[+] simmons|8 years ago|reply
I'm personally a big advocate of Rust. That said, like yourself, I'm also a little skeptical that it's the right tool for most web apps. I think a case can be made for small Rust web apps embedded in consumer electronics that may be very resource constrained, though. (Granted, these probably aren't the sorts of apps that would be using PostgreSQL as described in the article.) I've seen a lot of great web applications written in Go that can also be quite compact (compared to Java, for instance), and I think Go might be a great candidate for the next level up in resource availability.

Like many languages, the syntax becomes a lot more clear after you've used the language a while. Maybe it's me who's been warped, but I've been programming in Rust for long enough that the example you pasted looks perfectly normal. ;)

[+] qaq|8 years ago|reply
Well Rocket is about as verbose as express:

#[get("/<name>/<age>")]

fn hello(name: String, age: u8) -> String {

    format!("Hello, {} year old named {}!", age, name)

}
[+] pcwalton|8 years ago|reply
You can write an overcomplicated framework in any language. Some of those Java frameworks are pretty impenetrable too.

Here's an example of some Rust code that's even faster and is short and sweet: https://github.com/tokio-rs/tokio-minihttp/blob/master/examp...

(I don't think the TechEmpower benchmarks really mean much, incidentally. They're so simple that they're way too easy to game.)

[+] sgift|8 years ago|reply
> Rust is a good language but I don't see it any time soon in the web space with that syntax and the lack of mature libraries, plus the time it takes to get anything done.

Lack of mature libraries is always a problem, syntax is subjective and "time it takes go get anything done" .. which time do you measure? Time-to-first-running-example or Time-to-debugged-productions-ready-code? I have my doubts that Rust takes significantly longer for the second one.

[+] yani|8 years ago|reply
The syntax is not that bad but there are some poor variable names that make the code more difficult to grasp.

The motivation to learn a new language and the excitement during the process are huge factors that will push many people to write a web service in Rust.

[+] mseepgood|8 years ago|reply
Rust code is 80% technicalities and only 20% actual domain.
[+] oblio|8 years ago|reply
(Maybe Steve is around)

Why did Rust go with the IMHO ugly closure syntax of

    |stuff|
?

I think that

     stuff ->
is much nicer to look at... :(

Also, not presented here, but ‘ is also kind of ugly, was there no short keyword that could have been used? “life or “lt” for “lifetime”? “span”?

[+] adrianN|8 years ago|reply
To learn Rust?

Also, where do you get that the performance is the same? I didn't see any benchmarks.

[+] _wc0m|8 years ago|reply
I've been using 'actix-web' in anger for the last month or so and I just want to echo that it is an amazingly fast and fully-featured project.

I smiled when reading this post because I absolutely recognize the euphoria of carrying out a world-changing refactor and having it work first time. If this is what the future of backend development feels like, sign me up!

[+] dorfsmay|8 years ago|reply
Have you look at rocket etc... Curious about your decision process.
[+] illuminati1911|8 years ago|reply
Really nice article.

I’ve been using rust moderately for the last 6 months and at least my experince was that once you get used to playing along with the compiler and understand the fundamental features of the language, it almost feels too good to be true.

Perfect mix of high performance, safety and modern features. Slightly challenging at first, but it really is worth the initial pain.

[+] oromier|8 years ago|reply

[deleted]

[+] lmm|8 years ago|reply
The author briefly mentions Haskell but I wonder whether they've build web services with it, or with another (loosely) ML-family language (e.g. OCaml, F#, or my personal favourite Scala). An ML-style type system is a huge advantage over Ruby/JavaScript/..., but for a web service I struggle to see a real case for going to all the effort of memory ownership tracking rather than using a GCed language with the same kind of strong type system; it would be good to see a comparison from someone who's done both.
[+] nicoburns|8 years ago|reply
I think that despite the constraints imposed by the lack of GC, Rust is seen as more approachable than more functional langauges to those coming from a C/Java/JavaScript etc background. And the only similar language with an ML type system is Swift which isn't quite there with libraries for backend stuff.

Also, have you seen the sample code for Rocket based webapps? I haven't seen much nicer in any language. The async ecosystem isn't nearly as nice yet, but it should get much nicer once async-await lands.

[+] Shoothe|8 years ago|reply
> It’d be fair to say that I could’ve written an equivalent service in Ruby in a tenth of the time it took me to write this one in Rust.

Ha, I had the same feeling when I needed to create a simple REST service that'd just process a request using command line tools. Seems easy but Rust's tooling is full of rough edges. Code completion sometimes works sometimes doesn't, cargo cannot install dependencies only (without building source) so it's not good for Dockerfile layers, etc. etc. Borrow checker is not bad at all for people that worked with languages with explicit memory management (where you do ownership tracking in your head anyway).

Long story short I spent 2 days working on the service and had 80% done but figured out the rest would take twice as much if I want this to be production quality. I scraped the project and rewritten it in node, using one or two dependencies in 2 hours.

I'll be trying Rust again surely and I'm glad that articles like this one exist!

[+] vardump|8 years ago|reply
Very exciting to see Rust gaining support lately.

That said, one of the only remaining things keeping me from using it is production ready gRPC library.

[+] kehtnok|8 years ago|reply
Just an FYI (if you want something to watch and haven't heard about it already) https://github.com/tower-rs/tower-grpc is in the works by the same folks building tokio and hyper etc.

But yep, definitely not production ready as the README clearly states :)

[+] bluejekyll|8 years ago|reply
Thank you for writing/posting this!

Specifically the custom bindings using Diesel. I was unaware that generic sql could be bound so easily. That’s closer to how I end up when needing to create higher performance queries. I need to take another look at Diesel now.

Very nice article.

[+] staticassertion|8 years ago|reply
Thanks for writing this. Actix is really cool but I haven't really spent the time looking into it - I was unaware of the SyncArbiter abstraction.

This is a very helpful post for me.

[+] ComputerGuru|8 years ago|reply
I love rust, but to be honest, until the nightmare that is tokio/futures is fixed with native async/await and better compiler error messages, a strongly typed language like C# with those features natively present is my choice for web services. It addresses all the author’s issues with Ruby and JS, and is still orders of magnitude faster than those options (though admittedly not as fast as a C or rust option).
[+] rabidferret|8 years ago|reply
Just wanted to mention that your usage of `sql_query` is exactly how it's meant to be used, and exactly where I would reach for it. Great article!
[+] hardwaresofton|8 years ago|reply
tldr; - it was hard for me to determine which rust web framework I should be using, since I want something light like flask. best resource was https://github.com/flosse/rust-web-framework-comparison

Very recently I've taken a tour around the Rust web server ecosystem, and I think it's way too hard to find one to pick.

The author mentions actix-web[0], and mentions how fast it is, but it's actually right below hyper[1], which I find to be simpler, because it doesn't bring in the whole actor frame work thing. Hyper builds on Tokio[2] which introduces the usual event loop paradigm that we're used to in other languages (which AFAIK is the fastest way to write web servers to date). There's also great talk[3] that introduces the choices that tokio makes that are somewhat unique.

Here's what I want out of a web framework:

- express like-interface (func(req,resp,next) pattern provies just the right amount of freedom/structure IMO)

- good routing (plus: decorators/a fairly ergonomic way to specify routes)

- reasonable higher-level interfaces to implement (I want to implement an interface, and be able to receive things like logging, tracing, grpc/thrift/whatever transports for free, good interfaces are what make writing reusable stuff like that possible)

Here's what I found about the various frameworks that exist out there:

- Rocket.rs[4]: Seems to do too much (I tend towards building APIs), rocket is closer to django/rails than it is to flask/sinatra.

- Gotham.rs[5]: Seems perfect, but falls flat on the feature front, half the stuff on the landing page are features of rust, not the library. Doesn't seem to have enough batteries included, for example there's currently work being done on streaming request bodies (https://github.com/gotham-rs/gotham/issues/189), that's not a issue I want to run into.

- Iron.rs[6]: The oldest of the bunch, but also very very fast (which I discovered actually browsing rocket's issues[7]), not based on hyper, and also not actively maintained.

I had no idea which of these to use, or how to find ones I've missed, then I stumbled upon this amazing resource: https://github.com/flosse/rust-web-framework-comparison.

However, when you look at that comparison, it's really suspicious how much of actix-web has filled out, which is often indicative of something written from one point of view (like when people have comparison pages, and one option seems to just have "everything"). And again, like I said actix seems to be doing too much, I don't really want to utilize the actor model, I just want a relatively fast, ergonomic simple library with the most basic batteries included.

BTW, everyone keeps raving about type safety and I wonder why people don't give Haskell more of a go. If Rust gives you the type safety equivalent to a shield, haskell is a phalanx. I've never felt more safe and protected by my types than when I write Haskell -- it's relatively fast, memory safe, has fantastic concurrency primitives, and though it can be tough to optimize (which comes to down to optimizing for lower amounts of GCs like most other memory managed languages), it's pretty fast out of the gate. I use servant (https://haskell-servant.readthedocs.io/) and love it.

[0]: https://github.com/actix/actix-web

[1]: https://hyper.rs/

[2]: https://tokio.rs/

[3]: https://www.youtube.com/watch?v=4QZ0-vIIFug

[4]: https://rocket.rs/

[5]: https://gotham.rs/

[6]: http://ironframework.io/

[7]: https://github.com/SergioBenitez/Rocket/issues/552

[+] kbenson|8 years ago|reply
Wow, thanks a lot for this. It's definitely helpful, especially the link to the comparison which has a lot of resources and examples.
[+] huntie|8 years ago|reply
Iron does use hyper, but it uses an older, synchronous version of hyper. Iron also isn't actively maintained.