Writing a website in Rust
blog.viraptor.info
blog.viraptor.info
Rust is a great systems language (besides the lack of bitfields) but I wouldn't use it as a web language. Just as much as I wouldn't use C++ to develop webapps.
Sure you can do it but then again there are far easier ways to achieve your goal.
But honestly, I came away surprised how good the experience was. I wouldn't start a big team project in it (at least not until more common middleware is available and some higher level frameworks), but when rust 1.1, or 1.2 lands I'll definitely consider it for http microservices or RPC endpoints.
Go is strongly typed, and so is it's templating language. This means that you need to take a much more rigorous approach to using them, as a single unhandled edge case value can break the entire page. Contrast this with something like Jinja2 where if you mess up it'll only break a small bit of the page.
I managed to produce a usable template system, but it's still a tad more verbose than I'd like. It'd have been nice if a more scripting-language style templating language had been an option.
Do you have some examples of error that are easy to make with Jinja2? I personally find it to be a thing of beauty, but it's possible that I don't try to make very elaborate things with templating engines and thus might be missing out.
Ability to convert between language types and raw addresses, in the form of pointers.
Toolchain should also provide the ability for aot compilation both to static and dynamic executables/libraries.
In this context, Rust's semantics do allow C-level performance- they make it straightforward to describe the same machine code that C would. In some cases, they even enable more compiler optimizations than C does.
Java, on the other hand, makes it hard or impossible to do that (straightforwardly at least). A more mature compiler will make a lot of difference- compare early Java performance to where it is now, for example.
Please share your measurements of "real-world performance" that are "not very correlated" with the benchmarks game's results.
edit: Please provide a better response than downmods.
edit: Please provide a better response than downmods.
Just a URL to published comparison measurements would be so much more interesting for anyone interested in Rust, than downmods.
(There seem to have been plenty of breaking language changes in the last 6 months.)
(The "faster" bit isn't important; various reasons might have made C++ slower than Rust for this, but what is important is that perf was comparable)
What I questioned was Rusky's technical-sounding but apparently unsupported claim that "the benchmarks game's results are not very correlated with real-world performance." For Rusky to know he'd need some kind-of "real-world performance" measurements and a correlation coefficient - neither of which have been shown.
I'm working on improving the regex engine now. Hopefully I can submit some improvements soon. :-) It will be hard to get near the top, but I think there's some low hanging fruit I can tackle first.
A couple of weeks ago you said -- "… optimizing performance hasn't been a focus, shipping good language semantics has been."
https://news.ycombinator.com/item?id=9565405
edit: Please provide a better response than downmods.
Optimizing the workloads tested on the benchmarks game has not been a focus.
https://news.ycombinator.com/item?id=9554676
edit: Please provide a better response than downmods.
It isn't bad manners to trust that when steveklabnik answers "No, as optimizing performance hasn't been a focus, shipping good language semantics has been." that's what he means.
Conclusion: Don't draw conclusions from the benchmarks game.
[1] http://benchmarksgame.alioth.debian.org/u64q/performance.php...
[2] i5-3470 @ 3.20GHz
the answer was to what qualities make a lang suited for systems programming not just specific to Rust
And the minimal libraries usually need to be light weight as well. Or at least such that you can strip out what's not wanted. Here again garbage collection becomes an issue. If the language supports it natively then it's going to be impossible in practice to prevent the libraries from using it for something unexpected.
The last bit is escape. There are always some cases where the language doesn't have the ability to implement a critical bit of stuff and you need a way to seamlessly mess with that something. In simple you need to be able to insert a bit of arbitrary code and have it work properly.
Erlang would argue with you. GC works fine as long as you have a bunch of really tiny heaps (attached to a bunch of really tiny processes) which can be collected in parallel. It just sucks for Unix-process-sized heaps.
So, I'd rather use a higher-level language (and do) for most web-application development. I also love Python for its tradeoffs, esp productivity & readability. Yet, there are valid use-cases for native web apps esp where performance or memory-usage matters.
There is another advantage - I mostly write python for the 'glue' scripts in large build systems. It saves some complication if your build system doesn't first have to compile chunks of itself!
Meaning, c++ semantics still require more care to have a running program, even a low-LoC one ?
If you can specify bit endianness, byte endianness, spans of bytes, and so on... bitfields are mighty nice.
> Another bad part is Rust’s JSON handling. It badly needs macros which make things easier.
Actually this is not the end of the world. As you mentioned, Rust's JSON library supports `ToJson` for primitive types, but also provides compile-time code generation for arbitrary `struct`s. Quoting the code from my project:[1]
#[derive(RustcDecodable, RustcEncodable)]
struct Msg {
cmd: String,
id: i32,
x: Option<i32>,
y: Option<i32>,
speed: Option<i32>,
}
What the mystical `#[derive]` thing does is to direct the compiler to create the boilerplate for converting the `struct` to/from a string automatically. So, now you can do this: // Decoding a JSON string
let text = r#"{"cmd": "move", "x": 5, "y": 10, "speed": 200}"#;
let decoded: Msg = json::decode(text);
// Encoding a Msg object to JSON
let msg = Msg { cmd: "new", x: 10, y: 20 };
let encoded = json::encode(msg);
The `Msg` struct is full of `Option`s because my project allowed many fields to be missing, but if the JSON messages in your protocol are in fairly similar format, you can eliminate most of them.One more benefit of this approach is that it type checks. If the JSON you received is missing some fields that are not defined as `Option`, the decoding process produces an error! So you can be certain that you're handling a valid JSON after the decoding stage. This is analogous to schemaless vs. schema-enforced database design.
> Compared to many static languages, the handlers look tidy. Compared to dynamic languages, they’re terrible.
This is the property of the Iron framework, not of the Rust language itself! Another web framework for Rust, namely Nickel.rs[2], provides much simpler APIs.
I believe the author of Iron is trying to establish essential things first, and build more "user friendly" APIs on top of them. (FWIW, the Rust project itself follows the similar strategy.)
[1] https://github.com/barosl/pgr21-online-server/blob/master/sr...
Pardon my self-promotion: if you just need some one-off JSON and are willing to use the nightly compiler, I wrote a compiler plugin that expands JSON-like literals into an expression that expands to the tedious way of building up a JSON object.
https://github.com/tomjakubowski/json_macros
I find it's super handy for testing libraries or applications that emit JSON meant to go across some well-specified protocol. Some day I'll get around to expanding the library to support pattern matching with JSON literals as well (pull requests very welcome!).
#[derive(RustcDecodable, RustcEncodable)]
enum Msg {
Basic { cmd: String, id: i32 },
Positioned { cmd: String, id: i32, x: i32, y: i32 },
Speed { cmd: String, id: i32, speed: i32 },
Full { cmd: String, id: i32, x: i32, y: i32, speed: i32 }
}
That would allow you to prevent weird cases like x being provided but y not being provided. { "variant": "Basic", fields: ["cmd-value", 42] }
{ "variant": "Positioned", fields: ["cmd-value", 42, 0, 0] }
//etc ...This is interesting. First time I read about Rust I had this gut feeling that it's design might reduce the number of unit testing cases. Someone care to comment on that?
- Proper behaviour if I don't pass a parameter. Not needed, because it's an explicit `Option<>` which I have to handle.
- Is results list constructed properly / what's the None-vs-empty behaviour. Not needed, `Vec<>` is verified at type level and None is not possible.
- What happens if various database functions don't find a result. Not needed, because type signatures force either a returned `Entity` (if it's not there, it's an implementation bug: panic + 500), or `Option<Entity>` which again needs to be explicitly handled.
But these are only unit tests. If it was a critical app, I'd still write functional / logic tests to make sure the right data goes all the way to the database and back in known scenarios. Types can't guarantee that.
One kind of tests I'm tempted to write is for templates rendering properly, because handlebars-iron takes parameters as Json - all type guarantees go out of the window there. But it may be the same amount of effort to migrate to some type-safe templating like Maud.
The Option type alone handles so many cases which would otherwise require unit-tests in Python or another unityped language.
Rust is harder to get to compile, but a lot of your boilerplate testcases go away. And if you design your API right, even higher-level guarantees can be provided statically.
A lot of times when writing Rust code in Servo I'll just ensure that stuff compiles, and then run the tests once before making a pull request. When contributing to python codebases I generally make smaller, incremental changes and run tests.
Edit: Just to clarify, this doesn't mean you should go all #yolo and abandon testing entirely. But (a) you can do so temporarily without adverse effects, and (b) once you start, you'll find that you only need to focus on the higher level tests.
A lot of unit testing is verifying failure modes. What happens if they pass in null? What happens if they ask for a value i don't know about? stuff like that. Haskell (and rust) give you a lot more control over what a function is willing to accept at compile time.
You could spend your time writing a test to ensure null is handled gracefully. With rust, you have a bit more power, and you can simply ensure the function can't be called with null. It's more general than just null checking, but that's the flavor of what happens.
I intend to write another post with some very basic skeleton / hello-world of a new Iron application using database with connection pooling, templates, logger, parameter parsing. But that's for another day.
Lots of packages for it on code.dlang.org as well.
That experiment among others, of course.
There also was [this plugin](http://www.reddit.com/r/rust/comments/2krdbu/rest_easy_a_lin...), but it's outdated at the moment (I can upgrade it later).
Regarding imports, `use foo::bar::*` works.
So what in a dynamic language could be:
a.to_json() if a else {}.to_json()
In rust will start with: use rustc_serialize::json::{ToJson,Json};
use std::collections::BTreeMap;
...Does the Rust macro system require compile-time compilation before type-checking?
Rust macro-esque things are of three types:
- Macro by Example (MBE): These are easily defined by the user via `macro_rules!`, which can match on their input and expand to some output at compile time. These don't need to be defined as a plugin; you can define a macro directly in your code.
- tokentree expansion plugins: These take in a token tree, run arbitrary code, and output an AST (syntax tree) node. Ish.
- AST expansion plugin: These take in the parsed AST, run arbitrary code, and output another AST to replace or augment it.
There also is support for custom lints and llvm passes.
All of this is at compile time.
In fully-blown compile-time meta-programming systems such as Converge or Template Haskell, the "AST expansion plugin" can and typically does itself invoke the compiler to compile arbitrary code which in turn outputs a new AST for further compilation.
I'm not fully sure what macro-by-example is.
I think it's a pretty good architecture overall. We'll see how it all shakes out.
It also stores the index via git and S3...
Recently where I work we switched to Golang for any network facing daemons and it's been awesome. Rust was decided as 'too alpha' to try yet.
What do you mean by "tooling for the web"? If it's preprocessors like LESS (that are written in JS), you could run the JS in V8 to transform the CSS efficiently, but maybe the compiler "binary" (script) is enough. In this case it's the best approach, since performance will be good, and you don't have to reinvent everything.
But if it's something like express.js, it's probably not worth it.
It's surely possible somehow, but in a pretty hacky way and at the cost of high overhead and complexity caused by calling from a static language to a dynamic one.
There are two libraries that embed V8 in Rust, but all of them seem to be abandoned for now. However if there become enough interests, a new project will arise I think.
(FWIW, I ended up calling CommonMark library written in Python from Rust, because embedding CPython was easier than embedding V8. Unfortunately CPython's performance is worse than V8, but as I know Python better than JavaScript it was a reasonable choice.)
I haven't tried it, but I would expect it would be easier to link with Rust than an entire other language VM.
Also the third-party CommonMark libraries are usually easier to extend, with your own syntax addition.
I've done one website in C however and can tell you that Rust is way more predictable ;-)
I think it's the same kind of folks that create a huge Java object model, always one file per type. Gives the feeling of being big and doing real work, even when you're not.
Rust is very popular now. That doesn't mean it has a great design. One of the very first design decision/belief they made was "no code can perform with garbage collection". Well, garbage collection does cause quite a few performance problems, but that doesn't mean it can't work if you engineer it right.
Personally I think Go and Nim are better languages as a general statement, both for applications and systems programming.
I know I am going out on a limb making a negative statement like that, and previously Nim developers have tried to discourage me from saying negative things about Rust. Those guys are smart and have social skills and they realize they should be careful to be nice to Rust developers because sophisticated developers looking for things like performance, type safety, etc. with a C or C++ background are really going to benefit from Nim if they give it a chance and Rust developers are prime candidates for Nim conversion.
So let me just say clearly that I don't associate with the main Nim community, the core developers, or anyone really. Nothing I say here reflects on them I hope.
I literally have no friends in fact.
I don't understand people, or pay much attention to them, or interact with them very much. When I do interact I say what I really think, not what people want to hear.
What I DO understand is technology. From a very early age I have been programming in everything from different types of assembly language to C to C++ to Ruby, Javascript/CoffeeScript/ES7, different variants of SQL, Rust, Forth, OCaml, etc.
Mozilla is a leading technology organization with many contributors. Rust is a new technology with quite a few people innovating on it.
Unfortunately, Rust starts with a bad design decision and never really recovers from that design decision. The values and perspective are lacking a modern, contemporary perspective.
The Rust worldview is trapped in the C++ era.
I have been primarily a JavaScript developer both on the front and back end for many years now. Why? Because I like to make useful applications and I am sane and paying attention.
But in a world where people were better informed and had better judgement and the best things won out rather than the most popular, Rust would be an obscure language being toyed with by a few academics for (perhaps?) implementing certain parts of kernels, the new browser from Mozilla would be fully peer-to-peer capable (using IPFS/gittorrent/swarm/ndn etc), written in Nim of course, use JIT/VM/something Nim for scripting, and have thrown out JavaScript/ES6/ES7 AND CSS for good. Of course, in this ideal world there would be no google monopolizing all advertizing and capturing most good engineering talent, since semantic markup and p2p query would also be be built in to this browser, making google irrelevant. The default mode for the browser would be virtual reality, with the ability to render 2d operating systems or markdown/images on arbitrary 3d surfaces.
But instead we have the world we have. All advertising must follow the dictates of a giant all-powerful global corporation. Mozilla is trapped in a pseudo-C++ mindset and spending most of its efforts trying to reproduce the intractable mess of decades of CSS hacks, in the time the engineers have left after going through in excruciating detail all of the possible ways to 'borrow' memory. CSS, a brilliant system that is a pain the in the ass not only for programmers but also designers, so complex that only two computer programs in the world are known to do a reasonable job of rendering it accurately.
Its time to stop dragging the old tools, mindsets, and technical debt forward. Stop judging things on the basis of momentum or authority (Google/Mozilla/Microsoft) and start using your brain to select things rationally.
Mozilla as an organization is not going to be capable of admitting they made a mistake with Servo/Rust and recoding it in Nim. Just like the world is not going to accept that we should throw out CSS and use any other simple system that can reproduce graphic designs. And we are not getting rid of JavaScript with its horrible threading model and garbage collection anytime. But we should. In a sane world we would learn from our mistakes and throw all of that out.
That's not Rust's philosophy. Garbage collection is great—when it makes sense to use it. Rust is designed to make garbage collection optional, following the way systems software has been designed for decades.
> Mozilla as an organization is not going to be capable of admitting they made a mistake with Servo/Rust and recoding it in Nim.
I've already gone over multiple times why Nim would not be a good fit for Servo (which is not to say that Nim is a bad language, just that it would not be a good fit for Servo).
Here's a post about removing GC from core rust 2 years ago: http://pcwalton.github.io/blog/2013/06/02/removing-garbage-c...
Next: they didn't kill GC. They removed it from core, because rust is capable of having gc implemented as a library. Standard library still has two gc-enabled pointers - Rc and Arc. You can use them in current code to have garbage collected values.
Most systems software gets by fine with a combination of thread-safe and thread-local RC. Reference counting is a form of garbage collection that works really well when it's used only for the subset of data that needs GC--which is the style that Rust encourages anyhow.
For Servo we need a javascript engine. We're already using Spidermonkey. It has a GC for Javascript; we're already paying those costs. The Rust-side representation of DOM objects is also managed by the GC; that makes sense because these are tied to Javascript things.
We don't use the GC elsewhere. I think at some point we did, but the only place now in Servo where GC is used is where the data is strongly connected to data already managed by the SM GC.
Nim's GC is for general-purpose use in a language. Spidermonkey's GC is for GCing javascript, which already has an extensive runtime (which the GC ties into heavily). "Nim's GC is better than Spidermonkey's" is a statement of no value (and oversimplifies the situation) unless the context is specified. Using the SM GC to collect random Rust objects would be a bad idea. Somehow rigging up spidermonkey to use a Nim-like GC in Rust (all other things being the same) would also be a bad idea. Two different scenarios, two different GCs.
At the same time, we're putting a lot of thought into how to properly add an optional tracing GC. It's important that it doesn't impact the no-GC case, which is still, of course, primary.
The current reference counted types don't just get used for convenience, they get used because they describe the actual life cycle of the data they contain. A tracing GC would be similar.
Gc is "just" an automatic memory management. Rust is not a garbage collected language, but it does have optional garbage collection available.
The decision was only done at the point when the devs were confident that the ownership/borrowing system could manage all the normal workloads done by GC, and that GC could now be implemented well into the language as a library.
As a side note; it's really easy to see the wrong in things. It takes more work to try to find the good in things. You might find that you're better able to connect with people by being more optimistic.
The fact that using Rust to write a simple website isn't very handy doesn't mean it's a bad language. Web development simply isn't its primary focus: this is a systems programming language we're talking about. The mere fact that it is being considered for writing web apps is impressive since Rust is supposed to be a better C++, not a better Ruby, Python or Node.js. Rust mainly emphasizes performance and safety. This means all the 'magic' that happens in a dynamic language is exposed to the programmer, and has a cost: the code is more verbose, and seemingly simple things are more complicated.