Files
kasperhermansen-blog/content/posts/2024-07-12-building-rust-services.md
kjuulh 4bb6b0228a
Some checks failed
continuous-integration/drone/push Build is failing
feat: add blog contents
2025-07-31 11:01:22 +02:00

236 lines
12 KiB
Markdown

---
type: blog-post
title: Use Rust for Web services
description: 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.
draft: true
date: 2024-07-12
updates:
- time: 2024-07-12
description: first iteration
tags:
- "#blog"
---
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:
1. User creates module for handling user state
2. User gets compile error because memory is shared between threads.
3. User reads up on what tools are available to solve the problem
4. User uses an Arc<Mutex<...>> to share memory, and lock access so that nobody
can touch the data while someone is modifying it
5. 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.
6. 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.
7. The user has to refactor the entire app at once to make the migration
successful
8. 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.