Can’t stand Rust these problems, I switched the backend to Go

Author | Anthony Oleinik

Translator | Hirakawa

Planning | Liu Yan

This article was originally published on the Level Up Coding blog.

Don’t get excited! I can feel the anger you harbor when you click on this post. I don’t hate Rust – I tend to use it in many scenarios. All programming languages ​​are a means to an end. However, Rust wasn’t a good fit for the scenario I was dealing with, and I had to rewrite the project in Golang.

This project is a simple backend webhook service for Hasura. You may not know Hasura, which is a Postgres database wrapper that provides a GraphQL API out of the box. This is very handy for someone like me who develops a personal hobby project alone: ​​writing each REST endpoint or GQL parser would be too time-consuming, and the CRUD operations for each model are basically the same. It’s not as effective when some more complex logic is required – for this, Hasura allows you to map GQL requests to custom webhooks. For example, this is how I do S3 file uploads or authentication.

Problem 1: Dependency injection is difficult

Rust dependency injection is an interesting problem. If you need a concrete type like:

 fn do_stuff(db: &Database) { db.create(Stuff); db.       db.read(Stuff); }

You must pass a Database instance to do_stuff; no exceptions! You can’t “subclass” Database (Rust has no concept of subclasses). So, if you’re a programmer who doesn’t test the code yourself, then that’s perfectly fine; in fact, you’ll only have one implementation of Database, so there’s no reason for this function to accept anything other than Database.

What about us testers? We have to rewrite the function signature. Database needs to be a trait type, and then we implement that on the mock object. Well, not too bad. In fact, in Golang, I’m doing basically the same thing; so where does the problem start?

Problem 2: Asynchronous Traits

In Rust, async is easy, and so are traits, but async traits are a bit more difficult. Most examples of async traits I’ve found in Rust use the async_trait macro. This is very helpful, I am using it and the experience is not bad.

Here’s a summary of my process so far:

  1. Write a struct; be happy.

  2. Write a test; realize that dependency injection is not possible. sad.

  3. Convert struct to trait; be happy.

  4. Dependency injection to your heart’s content.

  5. Use the mockall crate to automatically generate mocks. Very very happy!

  6. Make an asynchronous http call.

  7. The async trait needs to be implemented with a special macro.

  8. Realized that this macro doesn’t work well with Mockall.

  9. sad.

In hindsight, there was a way around this problem. Maybe I should try again before switching to Go, but at that point, this is already getting me a bit frustrated…

Problem 3: Compilation is slow (fatal blow)

Rust has terrible compile times. We’ve heard it countless times; there can’t be an omnipotent language without flaws. That’s impossible – Rust’s downsides are incomprehensible lifetimes and terrible compile times.

I have a nice and durable laptop, the M1 Mac, which is an old scalper. I have absolutely no problem compiling Rust on my Mac. Usually, when writing a server, I develop locally and make sure that every time there is a change, the local server reloads, allowing me to test functionality very quickly before committing to real unit tests. A lot of compilation between trials; acceptable! Still, there is no problem compiling Rust on a Mac.

Is it in the container? just forget it.

For me, the easiest way to orchestrate many local services without bothering to run npm run in each service (Hasura, webhooks, mock s3, mock oauth server…) is to have a docker-compose.yaml Files can run all these things. Usually it’s just a docker-composition.dev.yaml file, since I don’t actually use docker compose for deployment. Only develop locally. However, this has the side effect that my Rust code needs to be compiled in the container because:

I had two options: either start a huge image that would kill the entire computer in order to compile the Rust code in it, or struggle with 3+ minute compile times. The development cycle stalled and made me feel very inefficient. I’ve tried changing the workflow, writing code and testing before manual testing, or not using automatic hot reloading, but alas, I just can’t do it.

Finally, I gritted my teeth and switched to Go. Nostalgic Rust: I absolutely love writing Rust code. I find it beautiful and expressive, functional and elegant.

If I’m writing native helper libraries, performance sensitive code, any backend services that don’t need to run in a container… then Rust would be my first choice. Especially if I don’t need to convince anyone else to use it.

Do let me know if you have any workarounds for the issues I mentioned, especially the last one. I want to get Rust back in the project, and I’m willing to go back to the old version and bring it up to par.

The text and pictures in this article are from AI Frontline

loading.gif

This article is transferred from https://www.techug.com/post/unable-to-stand-rust-s-problems-i-switched-the-back-end-to-godafcfd403f97ab9f287f/
This site is only for collection, and the copyright belongs to the original author.