12 KiB
type, title, description, draft, date, updates, tags
type | title | description | draft | date | updates | tags | ||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
blog-post | Use Rust for Web services | Rust is becoming a mature language, it is excellent at systems development. In this post I will explain why I think Rust is a viable choice for web services, and in the following posts build a simple web service to showcase all the required features I expect from a language. | true | 2024-07-12 |
|
|
Rust is increasingly becoming more mature, especially for systems development. Rust, however, can also be an excellent choice for building web services, which may not be obvious from the outset, in this post I'll show that Rust is a viable choice and does lend itself to web service development.
But, systems development
It is commonly said that Rust is built for systems development. That is, the core, nitty gritty details of our software stacks. That is definitely also where Rust shines, especially because Rusts values aligns quite well with systems development. That doesn't mean that Rust is unsuited for development in other areas than systems development. It means that we should carefully align what values Rust chooses to prioritize against our own.
Rust mentions on their website that their primary values are:
- Performance
- Reliability
- Productivity
If that fits your needs then I don't see any reason why you wouldn't choose Rust for building your business logic and serving requests.
System development typically favors:
- Performance
- Control
- Reliability
- Security
Which is why Rust is such an excellent choice for building core infrastructure.
Building web services is entirely up to you however, most companies I've seen favors:
- Productivity
- Reliability
- Catalog of libraries
- Ease of use
I won't go into each an every items for systems development, but I do think it is important to reason about why those 3 values are the ones I've chosen to select for:
Productivity
As software developers building business services, we're expected to deliver results. Software engineers working on front facing services, are often valued on how fast they can deliver results. It is simply how the business and technology work together. Business wants results, tech wants reliable, modern and understandable systems. Rust does select for productivity, but not in the way we usually think about it, when Rust says productivity, they say, we've got great tooling, a robust build system, package manager, auto-formatter etc. Built right into the tool you use the most when developing Rust. Rust does have it downsides for productivity, it has a rigorous type system, which requires fairly long compile times.
This is just my experience with Rust speaking here, but Rust really means productivity in the long run, delayed gratification if you will. It will sometimes feel like easy things are hard, but also that hard things are easy. In my experience with Rust, building web services from scratch can take quite a while, but doing refactorings later on, is fairly easy and reliable, simply because Rust is so rigorous. There is often the feeling that if it compiles it works, even if tests aren't run. Much more so than in other languages with weaker guarantees in its type system.
Reliability
Software engineers want to depend on tools that just works, they don't want crazy crashes, memory overflows, garbage collectors running amok, shared memory etc. Nobody wants to be paged at night, or spend weeks sorting out invalid data because a software service was misused, or data transformed in the wrong way.
Rust leans into reliability hard, it chooses reliability over nearly all other values. When Rust runs, it runs well, consistently, and fast. I've had services in production seeing traffic in all sorts of conditions and the programs keeps chucking along, some that have been running for months, just sitting there doing their thing.
Catalog of libraries
Software engineers working on business services are often integrating with a variety of systems. The language they choose either needs an incredibly vast set of libraries, and/or be excellent at interopating with other libraries. Hey, I need this program to send files to an SFTP server, I need a connection with this server, but it needs to be over mTLS, I've got these massive XML files that needs to be parsed. We've got this arcane library that we need for reason, please make sure to call it in this way.
There are tons of requirements for libraries, for common things such as logging, metrics, web apis, message protocols, message queues etc. Rust in my opinion sits at the early adopter/initial mainstream cycle here. While Rust has an incredibly vast library of tools, a lot of these tools are built by hobbyists, excellent quality don't get me wrong. But has currently is a second class citizen for a lot of providers. We are beginning to see services provide SDKs and so on for Rust, but often in the 2nd or 3rd wave of tools. Languages likes Javascript, Python, Golang and so on are still a little ahead.
Ease of use
As software engineers working on business services, we mostly spend our time on domain logic, talking to the business, architecting the apis for our customers, figuring out the right data paths for models, integrating with other internal customers. As such we want a simple language that should be powerful, but easy to use, we don't want to skimp on features, but we generally favor simplicity.
Rust does clash here, for one thing, it isn't easy to learn. Rust kind of has this unfortunate curve of learning, where initially you are fighting with the compiler to get your program built. As you lean more into the standard library you learn some bad practices, and use features to solve the short comings in dealing with Rust. Until hopefully when you've grokked Rusts model, can actually build quite simple software, that isn't overly verbose, or complex to use.
This sadly puts simplicity together with mastery of the language. While not impossible, it does make it difficult for a beginner/intermediate Rust developer to build simple, concise Rust code. Unlike, Golang, Python and so on. Which puts simplicity forward as some of their values. Golang especially makes it easy for even beginners of the language, to produce ideomatic, simple code.
Pretty decent overlap for web services
Rust actually seems from the outset to be pretty well suited for web service development. It is ergonomic, favors productivity, rock solid to use, and has a vast array of libraries. From experience Rust do have some issues for web service development. Primarily that isn't easy to pick up. This is a point I hope to help alleviate in this article. In my opinion a lot of the current use cases showing Rust, are in too technically complex domains, making it not as clear that Rust can actually produce quite simple code, for simple technical problems. Instead focusing on showing Rusts power in handling complex technical situations, especially with illustrating that Rust can handle these situations with effective code. Effective code is not equal simple code. And while from the outset, concise effective code looks simple. It often isn't, it requires deep knowledge of the language to operate, something we'd like to avoid unless the situation warrants it.
With this series, I hope to show some programming patterns for building services, which should hopefully put you in the right path. Some of the code will look trivial for the most part, as we're at the end of the day handling quite simple technical problems. It should, however, show that Rust while capable and often show wielding a sledgehammer, often it isn't warranted or the thing to use.
Why Rust is hard to learn
Rust is currently in this situation where it has a reputation for being hard to learn, which I'd sort of tend to agree with, as I struggled to learn it myself. I do have a single reason as to why Rust was hard to learn for me. I didn't have a peer to discuss problems with, no mentor for guiding me on the right path, etc. Lack of solid examples for doing the kind of programming I am interested in (primarily web service development and developer tooling).
I myself had to go the entire cycle, thorns and all, from beginner and intermediate in Rust, to having a somewhat good grasp on the language and ecosystem to be effective in the language. In a prior post, I showed how to build services in Rust as well. If I'd had some of these kinds of guides earlier then I'd certainly be much further in being productive in Rust.
Rust is obsessed with itself
The Rust community is crazy with the language, it sometimes feels a little bit like a cult. In the last year or so, I have felt it subside a little bit, at least in my local community. But in general, we're obsessed with the language, nerding out writing blog posts going into hairy problems, finding arcane tricks to solve the issue. Crazy debugging techniques to figure out why a certain program spent a few more cycles than it should on a given function call, etc. While entertaining, I do think we need to take a step back, and look at Rust for the values it brings, and see how we can promote in a more digestible format. So that the more casual side of Rust is shown as a contrast to the expert blogs.
Rust should be better at putting you in the pit of success
To succeed with Rust you need to be in the pit of success. You can't be rolling around at the sides like you can in most other languages. In Rust, if you're not following the rules, you will come to refactor your app, many times. Do tricks to avoid ownership rules, all tricks, that compound in the apps being more complex than required.
Rust has tons of rules of how you should write the language, it just doens't enforce them that strictly. Some are shown explicitly and implicitly in various material, The Rust Book etc. However, most of these rules, really have to be enforced to actually be followed. Most languages have implicit rules that you need to follow. C++ has a really long book about it, Rust has The Book etc. Golang also has implicit requirements. For examples that consumers should close channels, functions should return structs but accept interfaces etc. Golang feels like a language where it is easy to fall into the pit of success. Mostly because the language is fairly easy to understand, so it becomes easier to grasp the implicit rules.
Rust is hard to learn, as you are given a solution to the problem you face at hand. But that solution is nearly always a sledge hammer instead of what you should be doing, which is be consistent. Which is super difficult to do when you don't know what the right thing to do is.
The learning curve for a Rust problem goes somewhat like this:
- User creates module for handling user state
- User gets compile error because memory is shared between threads.
- User reads up on what tools are available to solve the problem
- User uses an Arc<Mutex<...>> to share memory, and lock access so that nobody can touch the data while someone is modifying it
- User exposes their services under Arc<Mutex<>> everywhere. Every class now need to take an Arc Mutex as input for one or more dependencies. Database, Logger, Business service. The code becomes a mess to maintain and refactor.
- User refactors the app, they find out that they should wrap state into consistent ideomatic modules that only expose the api requires. All thread safety mechanisms should either be exposed as APIs or maintained internally.
- The user has to refactor the entire app at once to make the migration successful
- The user can now share dependencies with references, the code is fairly concise, testable.
Rust has tons of rules, hidden in various syntax rules, that while boiling down to simple concepts are difficult to implement and understand, some of the rules even depend on the context they're in and which values your program selects for. If writing web services, you'll want to build modules that encapsulate their state and provides a shareable reference outside, that is cloneable and safe to use. Structs are preferred over traits as the API. We'll go into each of these in a more thorough manner in later posts as we encounter the need for them. If you're a Rust developer you may contest to these ideas, which obviously is fine, as I may be wrong :,). However, I hope that the series will show how I consistently build services in Rust, that are ergonomic to write and not overly verbose.