236 lines
12 KiB
Markdown
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.
|