I’ve spent a scary amount of time with #Ruby and #RubyOnRails (nearly 2 decades at this point) and given the recent developments in the community and circumstances in general vigorously gesturing at everything I though I'd explore what's out there.

I decided to try #Rust for web dev. I have to say that I dubbed my toes regularly into Rust but never used it extensively and never learned it properly. It seems I reread the Rust Book like every three years and I still can’t fully grasp it. I did Advent of Code this year with Rust. Finished all the tasks. So I felt I might try as well something else.

Anyway, I did a quick search. I got to https://www.arewewebyet.org/ — it says “Yes! And it's freaking fast!” so I was encouraged. I looked at what it recommends and off I went.

So here's a rare thread. I’m doing a series of posts because I want to vent a bit and "be wrong on the internet" in hope that someone’s gonna set the record straight and I will learn something. A thread will hopefully make it easier for the hivemind to address individual points.

PLEASE, REPLY TO THESE POSTS.

Anyway, here we go. 🧵

Are we web yet? Yes, and it's freaking fast!

AreWeWebYet gives insight on whether you can build your latest web-project on top of Rust

Are We Web Yet?

I had to start with something. I picked #Axum. It’s one of the most popular frameworks and it's actively developed. So good enough for start.

First I have to address the “framework” here. IT terms of scope Axum is closer to Sinatra than Rails. And closer from the opposite side. It's somewhere between Rack and Sinatra.

The central part of Axum is the router and API for request-response mapping into types.

So in Ruby pretty much every web framework has some sort of Request and Response objects. They completely encapsulate both concepts and you have to stick your fingers deep into Request object to get anything you may want.

You still can do that in Axum but generally your “actions” (in Rails terminology) are just functions. And arguments to those functions pieces of the request that you need to process the request. It can be a piece of the request path (like your post id from /posts/42), or cookies, or any specific header, or parsed json request body, or form data, etc. So where in Rails you get request (or at bests params) in Axum you get very specific and typed pieces of information.

Your handler may look like this:

fn create_post(Form(post_form): Form<PostForm>, cookies: CookieJar) -> Result<(), StatusCode> {
//…
}

Now, Rails docs are great. Specifically the Guides. You can learn every piece of Rails from them. There are no guides for Axum. There are API docs. They're very different to Rails docs. It will take some time learning to read the docs. But the main learning material seems to be examples. Examples are indispensable. But they're sort of terrible to learn all the possible types the handler function can take.

I had to actually go and lear the magic that makes this typed handlers witchcraft work to understand what I can plug in there. (It's explicit type coercion and a little macro.) I'm glad I understand it now but I have to point out that I spend much longer trying to figure that out reading the docs and still didn't until I read the code. It's not a good learning experience.

#Ruby #RubyOnRails #Rust

Now, that was request. For responses it's basically the same, in a way. Your handled has to return something that can be converted into a response. It’s literally impl IntoResponse.

Axum provides implementations for a few things like tuples of relevant pieces of information. You can return a bag or response pieces and it will glue them together into a proper thing.

It can be a redirect, or a status code and HTML string (HTML(“my html”), or a JSON string (JSON(serialized_json)). You can add headers in there. It can be a Result so you can propagate errors around.

It's very neat when it clicks but it's very hard to find out what actual types have that trait implemented for. So you have to gerp examples and dive deep in to the framework code to see if the trait is implemented for what you want to return or you have to come up with your own type and implement the trait for it.

It's not a good learning experience.

#Axum #Ruby #RubyOnRails #Rust

Since we're talking about responses let’s address the elephant in the room: HTML.

In Ruby pretty much every framework support some form of templating. Even barebones Sinatra gives you ERB out of the box and pretty much any other template engine is only one line in the gemfile away.

Axum doesn't provide any templating. You’ll have to bring your own.

I picked #Askama as it was the first templating engine on Are We Web Yet.

It's a dialect of Jinja. But maybe not 100% compatible. No matter.

One thing you learn fast is that in Rust everything is a type. So your template has to have an actual template and a struct that provides data for the template. You can't just put random code in your templates. On one hand it’s great as you can't by accident have n+1 queries in your template. On the other, to change something on the page you might to edit three files: the actual template, the struct for the template, and the handler to populate the struct.

There's no support for layouts. Well, there's kinda. Through template inheritance. It’s where you extend your layout by overriding some blocks in it. It's much more awkward than in Ruby. In Ruby layouts are conceptually separate. In Askama your template struct has to provide all the data for the template and all its parent templates. I don't even know if it's possible to render the same template in different layouts. The thing that is extremely easy in Ruby.

There's also no support for partials. At best you can insert renders of other templates. But that's awkward for many of the same reasons (mostly around providing data for the templates).

One major downside is that Askama uses proc macro to compile the templates to Rust. It makes it extremely fast but debugging templates is a nightmare. It gives you an error that something is missing or has a wrong type but doesn't tell you what it is or where in the template it is. Even with enabled proc macro backtraces it gives you a snippet of generated Rust code and not the template. Even the barebones ERB will give you the exact position in the template in case of an error.

So yeah, lots of frustration.

#Axum #Ruby #RubyOnRails #Rust

BTW, there's no form builder in Axum.

I’m not very fond of Rails form builder. It's bloated. It hides a lot of HTML features. But if you need a basic form it's just magical.

Axum also can only parse flat forms. By flat I mean, that only one level of struct nesting. Nothing like nested attributes in Axum. You have to bring your own crate for that.

#Axum #Ruby #RubyOnRails #Rust

Axum also doesn't provide any DB layer. You’ll have to bring your own. (Notice the theme?)

I’ve chosen Diesel.

At the surface it's kinda similar to ActiveRecord. You see familiar terms like migrations and associations. But in reality it's very far from AR.

Again, I'm not exactly a fan of ActiveRecord but in comparison experience working with it is all sparkly unicorns and rainbows.

Let's take migrations, for example. Migrations in Diesel are raw SQL. Which is fine is you're forking on an app that will only use a single db (like your typical startup or something will settle on, say, Postgres for the main db). It’s much more awkward when you're working on an app that people might want to deploy in different environments. Like a self-hosted app that can have 1-2 users might want to use SQLite for simplicity, and same app can be used for a company of 300 and might be better served by pg (for reliability, concurrency, backups, etc.). With Diesel you’d have to write migrations for every supported db backend. In Rails you use a simple API that abstracts that away for the most part. You still can use very specific db-dependent feature, in the same migration.

Diesel provides an option to generate migrations from a schema definition. I think it's neat. You write the schema you need and Diesel would figure out what needs to be changed and puts that in a migration. The issue is that the DSL is extremely limited. You can't even define indexes with it.

Relations in Diesel are very limited as well. It's very basic “get associated records”. Transitive associations are very awkward compared to AR.

Anther snag is in Rust everything is a type. On one hand Diesel ensures that db is in the right state to work with the types in your code. So you can be sure there won't be some weird miscommunication between the app and the db that will lose your data. On the other, you can't be loose with your requests. In Rails you can requests partial rows and you still get the same models. So you can optimise your queries much easier (but you have to be careful). In Diesel every shape of the returned row has to be a specific type. You can't randomly add or remove columns from the query.

So what’s conceptually a single model in AR can be a whole bunch of structs in Diesel. And you have to pay attention where you use which.

#Axum #Ruby #RubyOnRails #Rust

BTW, remember forms? Those have to be separate structs, too.

Where in Rails you’d have Post.find(id).update(params) you'd have three different structs in Rust: one for the form, one for the partial update, and one for the returned post.

So in addition to those multiple “query” structs, you also may get multiple input structs (for each different form, for each schema in the API, etc.). And you have to com up with conversions between those for everything to work.

#Axum #Ruby #RubyOnRails #Rust

At this point I’m nearly at my wits end trying to build a form for a record that has multiple associated records.

I haven't yet touched assets. I've seen solutions to embed assets into the compiled binary, which is neat. I like that the whole app can be deployed in a single file. I suspect the build for those assets has to be external.

I also haven't touched background jobs. As far as I can tell there's nothing like Sidekiq for Rust. Though maybe there's something decent. I haven’t looked yet

#Axum #Ruby #RubyOnRails #Rust

Oh, right, almost forgot. Axum provides router but doesn't provide anything to generate URLs. And as far as I can tell, it's on purpose.

In rails you get named routes and helpers to generate URLs for them. It a little thing but it helps a lot when you change the URL but keep the name. You don't have to go through the whole app and change it everywhere but you still get the new URLs.

In Axum it’s all just strings. You have to make sure you remember every place you have that URL and you’ll have to fix it manually.

TBH, I'm baffled by this. Everywhere else everything has to be its own type and types have to be coherent. But here it's completely detached and suddenly stringly typed.

#Axum #Ruby #RubyOnRails #Rust

OK, that's a wrap for now.

My current impression is that Rust is decades behind Ruby in terms of developer experience. There are some neat ideas, some features that are only possible because of Rust's type system, performance is definitely incomparable. But the things that are called a framework in Rust would never be called that in Ruby. Rust can not be compared to Ruby in terms of development speed.

I can not stress that enough. What's done in Rails with a scaffold and a few lines of code took me like a solid week and I'm still nowhere near the end of it. Yes, I have to learn a lot. But I have to learn a lot precisely because basics are not covered in the docs and I can't copy-paste pieces to have the same (or analogous) thing as in Rails.

It's rather the basics are different. In Rails basics are CRUD. In Rust basics are how to run a router on top of a socket. A form to update a record in the db is an advanced topic in Rust web development. Unfortunately.

#Axum #Ruby #RubyOnRails #Rust

@pointlessone Yes, proper mature frameworks for web backend don't exist in Rust. rocket is the only somewhat well-known one that even tries, and that's not seeing all that much development AFAIK.

I was actually going to comment that it's wrong to think of axum as a framework, went to our docs just to make sure we're not using that term and... it's right there in the first paragraph. Certainly going to change that.

@pointlessone Thanks again for your feedback :)
Posted a PR now to remove the 'framework' wording. Feedback welcome!
https://github.com/tokio-rs/axum/pull/3616
@jplatte TBH, didn't expect that. In the age of overhyping everything this is impressive.