Hacker News new | past | comments | ask | show | jobs | submit login
Sapper, a lightweight web framework written in Rust (github.com/sappworks)
127 points by dumindunuwan on June 1, 2016 | hide | past | favorite | 59 comments



It's good to see new web-focused projects in Rust, I think it can be a really great language for this kind of thing. But to be honest, I'm looking forward to seeing what will be considered the go-to approach for Rust web applications _in a year_ or so.

There is much experimentation going on right now, with cool little innovations here and there, but I have yet to see an approach that looks really new and Rust-y to me.

For example: Rust's type system certainly has some nice properties to track dependencies between middlewares[^1] but I'm not convinced middlewares are even the best approach possible here. Also, it will be interesting how a framework based on async IO will look and what tricks/syntactic sugar/macros will be employed to keep boilerplate code down.

This is a pretty big order, of course: While I enjoy using small libraries, a well designed web framework requires a lot of organisational effort for all the small pieces to fit together nicely. I think most of the 'small pieces' are already there or at least well under way, by the way. For example: The async (mio) branch of hyper looks pretty solid for HTTP1/2, the Diesel ORM is fantastic (and will, according to creator Sean Griffin, soon hit 1.0), and handling JSON will only get better with future versions of serde.

[^1]: As recently discussed in https://chrismorgan.info/blog/tween.html and some other posts concerning the use of sessions types in Rust.


> For example: Rust's type system certainly has some nice properties to track dependencies between middlewares[^1] but I'm not convinced middlewares are even the best approach possible here.

There's a good discussion of this on /r/rust: https://www.reddit.com/r/rust/comments/4lz92j/tween_a_middle...

I'm pretty interested in tomakas's approach, which seems to avoid the approach of middlewares, in favor of explicitness: https://www.reddit.com/r/rust/comments/4lz92j/tween_a_middle...


Fun fact: crates.io is written with a Rust + Ember + Postgres stack, though with its own framework, condiut, rather than Sapper. It uses about 30 megabytes of memory resident. Coming from Rails world, this was... quite the change.

Also fun fact: we had a small amount of downtime today :( Type safety can't protect you from everything. https://users.rust-lang.org/t/crates-io-is-down-fixed/6060/6...


The more I look at Rust the less I like the syntax, what are those? "Ok(())"


There are some eye-poking bits in Rust's syntax, but this one is not so bad.

`Ok(value)` makes an instance of the `Result` type. Imagine `struct Result Ok(some_t value);` in C.

`()` is a 0-length case of a tuple. Sort-of like `{}` in C's `int arr[] = {}`.

It's used here, because `Ok` is defined to take a value, so something must be provided. The compiler knows how to optimize out `()` completely, so the single `Result` type can be used for efficiently handling both cases returning a something||error and NULL||error.


() is the unit/void value. The void value isn't a concept that most languages have, so the syntax may be unfamiliar, but it's really useful and once you have it it's annoying to work with languages that don't have it.


It's actually fairly common in modern languages, particularly in expression-oriented languages. Haskell, Ocaml, Swift, and Scala spell it (). Python spells it None. Kotlin spells it Unit(). In Kotlin and Python, it is implied whenever you do 'return' without a value, which is a pretty handy way of bridging programmers' understanding of it when they come from older programming languages (eg. Java and C) that don't have it.


Python's None is closer to null than unit.


There isn't really a meaningful distinction in a dynamically typed language.

Null is a value that indicates the absence of something else that could be there instead, Unit is essentially the lack of any value. So when a python function implicitly returns None whenever it's called, that's semantically more similar to Unit than Null.


The difference is methods. A Result or Option has methods like map that let you transform it regardless of which case it is in, while a null will always crash unless you match on it first.


In that case, do you condsider Go's nil to not be a null, since you can call methods on it that can do whatever? Or how about objective C's nil, where any messages sent to it are simply ignored?

Edit: I see now that you're talking about the difference between null and Result/Option, not the difference between null/Option and Unit, so we're talking about different things.

Semantically, a value that could be data or null is equivalent to an option type. Option types have methods, yes, but that's just a superficial convenience facility, you could implement the same thing for nullable types by writing a function:

    public static <A, B> B map(A object, Function<A, B> f) {
        if (object == null) return null;
        return f.apply(object);
    }


Python also has ().

    >>> type(())
    <class 'tuple'>


I get what you are saying. The code is not very readable for an engineer well-versed is C-style languages.

That said, I was a systems engineer and did a lot of C++ but now don't really understand it anymore because these days it's incomprehensible to me especially after c++11/14 (and I coded in c++ for many years!).

Same goes for javascript - with es7, the language syntax is changing very much (and imo, not for the better). They just keep adding features for no reason.

I just hate that languages at some point try to behave they are some sort of library with non-stop changes :/ So, despite not liking Go syntax, I like the people behind Go. They don't want to change it too much for no reason (yes yes, we need generics). I like that philosophy.

In fact, C is pretty much the only language which has held on to it's core syntax to me.


Or this:

pub fn from_bytes(bytes: &'a [u8]) -> IResult<&'a [u8], C<'a>> { ... }

or something like this:

fn parse_response<'a>(bytes: &'a [u8]) -> IResult<&'a [u8], C<'a>> { switch!(bytes, tuple!(be_u32, be_u32), (::CONNECT_ACTION_ID, tid) => map!(be_u64, |cid| B::new(tid, ResponseType::Connect(cid)) ) | (::ANNOUNCE_IPV4_ACTION_ID, tid) => map!(call!(W::from_bytes_v4), |ann_res| { C::new(tid, ResponseType::Announce(ann_res)) }) |


I wouldn't take this as representative of normal Rust code. This is standard nom which I would categorize very much as a DSL of sorts for creating parser combinators. In order to understand this code you really have to understand the fundamentals of nom as the reason it is so powerful is because there is a bit of magic going on here.

Also, I knew this piece of code looked familiar. For those that wanted to see this code with whitespace, https://github.com/GGist/bip-rs/blob/master/bip_utracker/src....


You're missing a <'a> in the declaration there ;)

"from_bytes is a public function with one lifetime parameter, a. It takes one argument, a slice of u8 with the lifetime 'a, and returns an IResult, which gives back a slice of u8 with the lifetime 'a in the success case, and some type C, which is also parameterized with the lifetime 'a, in the failure case."

This is not really normal rust code; it's nom, which is a very macro-heavy DSL. There are decent upsides, and I love nom, but its downside is that it does look a bit odd.


It doesn't look a bit odd. It looks impenetrable.


Well, without all the whitespace, it is ;)


Seriously, you can't strip out the whitespace and then pretend to make reasonable critiques of readability.


It would be amazing if there were a parser that would translate Rust declarations into english. I think something like that exists for C, but I can't recall the name or how effective it is.



It's a bit ironic that something that uses a lot of macros becomes hard to read. Even the link provided by marquisee in a sibling common seems to have almost a ratio of 2:1 of "signal noise" (or (too) densely coded meta-code) to the amount of "actual" code.

Have there been any (recent) discussions on how to make this stuff friendlier? I mean, one could always just bootstrap lisp on top of ecl[1], like clasp[2] does, and either generate rust code, or just machine code -- but even for plain rust it seems it should be possible to this stuff in a friendlier way?

I'm wondering if it wouldn't be worth it to have a more verbose syntax and/or keywords for this stuff -- it really does appear to me as a usability barrier -- not just because it's new and strange. Reminds me a bit about perl's use of "sigils" which I never quite thought made a lot of sense personally. I'm firmly in the camp that if you want more syntax than common lisp or smalltalk, you need a really good reason for every bit you add. And while I like a lot of things about rust, I'm starting to wonder if this isn't an area that really needs improving. Kind of like how C++14 tries very hard to get rid of a lot of stuff.

[1] "Embeddable Common Lisp" https://common-lisp.net/project/ecl/

[2] https://github.com/drmeister/clasp


I don't think it's ironic; macros enable new syntax, so you'll see it much less than "regular" Rust code, and so it will look a little strange. What parts of that link do you think are "meta" vs "actual"? It's not like we just added syntax for the fun of it :)

"Just embed a Lisp" is very far from a "just", and, most non-lispers find Lisp very hard to read, specifically because there is no syntax.


Fair enough, "just embed lisp" was a bit tounge-in-cheek. I suppose what I mean is that lisp macros, while powerful, don't need to add syntax that is jarringly different from other syntax (although they certainly can). I suppose I think that stuff like:

  fn parse_response<'a>(bytes: &'a [u8]) ->
    IResult<&'a [u8], TrackerResponse<'a>>
    {
      switch!(bytes, tuple!(be_u32, be_u32),
     (::CONNECT_ACTION_ID, tid) =>
       map!(be_u64, |cid|
         TrackerResponse::new(tid,
         ResponseType::Connect(cid))
      ) |  (...)
Appears to be very dense in very terse type and life-time information, compared to the variable names, function calls/macro expansions.

I get that it might be necessary at times, I just think it is a worthy goal to strive to make code look somewhat simple, even when doing complex things.

Example, in this case, with all the lifetimes(?) being the same(?) 'a, could perhaps the compiler infer that? Would it be more useful to make it explicit when they differ?

That kind of thing.

[ed: As or it being ironic, I see powerful constructs like macros more as a tool for making complex things simpler, not to complicate simple things.

That's what I mean when I say "macro-heavy" code being hard to read is ironic. It could of course be that this example really is complex, but it kind of strikes me as looking more "complected" than "complex".]


  > I just think it is a worthy goal to strive to make code
  > look somewhat simple,
To be clear, I do as well. Some things are just inherently complex, though.

  > could perhaps the compiler infer that? 
The only reason they're added here is that they're not able to be inferred, and the reason they're all the same is that you're explicitly connecting the lifetime of each of these things.

  > it kind of strikes me as looking more "complected" than "complex".
nom is for building extremely fast, extremely low-overhead parsers. When you're trying to do stuff like that, you can't always afford convenience stuff. Consider if you had to write all the code the macros generate by hand!


> The only reason they're added here is that they're not able to be inferred, and the reason they're all the same is that you're explicitly connecting the lifetime of each of these things.

Is the default of having them all be implicitly different, or unlinked, lifetimes very useful?


If you don't write anything at all, the "lifetime elision rules" kick in: http://doc.rust-lang.org/stable/book/lifetimes.html#lifetime...

We used to only have the first of those three rules. When we added the other two, almost 90% of lifetime annotations in function declarations were able to be removed from the compiler and standard library.


It's an empty tuple stuffed into an enum variant called Ok. The empty tuple plays the role of the traditional void.


Importantly, it's the Ok variant of the Result<(), SomeError> type, which is richer than a boolean because it carries information about why/where an operation failed, not just whether it succeeded or failed. It's similar to (but not identical to):

    public void someOperation() throws SomeException { ... }


I like to compare it to the "comma error" idiom in Go, i.e., `result, err = doStuff(...)`. The Rust prelude's Result enum is a less ad-hoc version of that.


It supports that kind of error handling too,

fn something() -> (String, String) ...

let (result, err) = something()

It's just that Result is preferable because you can pattern match on it


That would be

    fn something() -> (Option<String>, Option<String>)
Because the Rust types aren't nullable by default. But that more clearly demonstrates that Result types are better since they enforce that the error and result are mutually exclusive.


agreed!


Right, and the Rust result also produces a compile-time warning (which can be escalated to an error) if the value isn't checked.


Interesting, when would I use this (and Rust) over some other new web frameworks like Amethyst (Crystal) and Jester (Nim)?

https://github.com/Codcore/amethyst

https://github.com/dom96/jester


Can someone recommend a good IDE or editor with good IDE-like features for Rust? I think it's time for me to take the plunge and start learning a little bit about Rust.

Also, I'm assuming the windows support is good these days?


Windows is a first-tier platform, and can be counted on to work since Windows is the largest platform for Firefox, and hence enormously important for Mozilla.

For working with Windows APIs, see https://github.com/retep998/winapi-rs


Every OS is a first tier platform for mozilla.


Mozilla is a business. They likely prioritize where they invest effort based on where their ad revenue comes from. Doing otherwise can hurt the business. kibwen implies that most revenue is from Windows users. So, Mozilla wants the Windows experience rock solid. That they have two, serious competitors on that platform reinforces that need.

Fortunately for us, they also put plenty of effort into making it work on less-popular platforms like my Linux desktop. I'm grateful even if it's lower priority. :)

Note: I'd be curious what OS's most Firefox and Rust developers use.


My impression is that the Mozilla developers are motivated by helping people and keeping the web open.

To answer your question about OSes, most Mozilla developers who I've met run Linux or Mac OS on their machines, but they also try to use Windows sometimes because Windows is such an important desktop platform.


I work for Mozilla and estimate the usage of Mac/Linux/Windows by Mozilla developers is probably 50/40/10% respectively, whereas the general population of Firefox users is more like 8/2/90%. Windows XP is still about 15%, more than Mac or Windows 10!


I can tell you that Servo, for the longest time, had no build instructions for Windows, so at least among the developers working on Servo, Unix seems to be the clear winner...


That question may be answered soon (although the survey is also targeted at people not using Rust):

http://blog.rust-lang.org/2016/05/09/survey.html

I think it's about to close in the next couple of days. wink wink nudge nudge


Well I've used Firefox on both Linux and Windows for years and the Linux version is better than the Windows version. More polished, faster, feels better integrated. The windows version is great too, but I believe a lot of the developers respect open source philosophy and therefore we get first class software for opensource operating systems. Chrome on the other hand sucked on Linux for a long time (but was really good when it first came out). Chrome is pretty good nowadays but still takes three minutes to start, whereas firefox just opens up.

I don't use a lot of other mozilla software except rust (a little) and from what I remember they had spotty support for Windows when Rust first came out, but this is true of pretty much all new languages unless Microsoft creates it.


> Well I've used Firefox on both Linux and Windows for years and the Linux version is better than the Windows version. More polished, faster, feels better integrated

Do you have any numbers on the "faster" aspect? Most comparisons I've seen put the Linux version solidly behind Windows version.

As for features, for example video playback on Linux Firefox has severely lagged behind Windows version. It's not very long time ago that I needed to make some "non-recommended" about:config changes to get youtube working.

Even integration-wise I think I have had more problems on Linux than on Windows, file associations, copy-paste, and misapplied themes being most notable issues that come to mind.

I'm pretty sure that I've seen even Mozilla say that majority of developer effort goes to Windows first, and it shows. While you anecdotally might have had good luck, I'd claim that is atypical


That's all true. I was referring to regular web surfing and such. Video and integration are substantially better on Windows.


I also thought Firefox ran better on Linux a bit. I decided to test your Chrome claim on my Core Duo 2 box running Ubuntu. I just downloaded Chrome from Software Center. First run took 2-3 seconds to usable browser. Something is wrong with your PC, distro, or installation.

"but I believe a lot of the developers respect open source philosophy and therefore we get first class software for opensource operating systems"

That's probably true to some degree. That the least, there might be more developers on FOSS boxes testing it which spots more problems. Hypothetical as I have no data on Rust to test it with.


Regarding your IDE question: https://areweideyet.com/



I'm using Atom (on a fairly speedy machine, VS Code has some solid plugin support too) with:

    * atom-beautify (with rustfmt installed, `cargo install rustfmt`)
    * language-rust
    * linter + linter-rust (using cargo check, `cargo install cargo-check`)
    * you-complete-me (but YCM is not fun to install, so I'd recommend the racer plugin, both require the Rust source cloned and installed somewhere)
Altogether, those plugins give a pretty good Rust experience.


Is this like anywhere even close to the functionality of something like IntelliJ? Doesn't really sound like it. Linting and formatting, and completion does not an IDE make. How much code intelligence and introspection is there?


Not nearly as much as intellij. However the "linting" in this case is really just highlighting for compiler warnings and errors which are quite good in Rust. That said, you can use clippy (https://github.com/Manishearth/rust-clippy) to get some pretty solid static code analysis as a compiler plug in.

Also, I am very closely following the progress of the rust intellij project, it's coming along nicely.


+1 for racer, I use it with atom and it makes writing rust so much nicer. can't comment on the comparison with YCM, though


YCM uses racerd (https://github.com/jwilm/racerd) which keeps everything in a daemon process's memory instead of reparsing every time. It's a good bit faster (at least on my machine), but it uses racer under the hood for recommendations.


sounds interesting, I'll have to give it a shot!


I've had a great time with Tokamak: https://vertexclique.github.io/tokamak/


Emacs and Atom seem to have the most complete offerings via plugins like Racer. I'm quite happy with my Atom setup, but it is pretty far from turn key.

1.) You need to `cargo install racer` and add the bin to your path

2.) Clone the Rust source of your version of Rust, and add that to your path

3.) Install the plugin and set the paths in Atom


most of the popular editors have racer(autocomplete) integration.

Personally I've found visual studio code to have a good mix of light weight and functional.


This framework seems to have practically the same API as nickel.rs https://github.com/nickel-org/nickel.rs




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: