Articles tagged #rust
Update: EuroRust introduces a freelancer ticket
I'm happy to report that the EuroRust organizers have listened, and they've come up with a solution.
I use the draw.io desktop app to
make diagrams for my website. I run it on an actual desktop, like Windows or
macOS, but the asset pipeline that converts .drawio
files, to .pdf
, to
.svg
, and then to .svg
again (but smaller) runs on Linux.
Disclaimer:
At some point in this article, I discuss The Rust Foundation. I have received a $5000 grant from them in 2023 for making educational articles and videos about Rust.
(Note: this was originally posted as a gist)
Reassuring myself about Rust
Up until recently, I was part of two private online discussion spaces where a bunch of Rust people hung out.
We've achieved our goals already with this series: we have a web service written in Rust, built into a Docker image with nix, with a nice dev shell, that we can deploy to fly.io.
There it is. The final installment.
Over the course of this series, we've built a very useful Rust web service that shows us colored ASCII art cats, and we've packaged it with docker, and deployed it to https://fly.io.
Wait wait wait, so we're not talking about nix yet?
Well, no! The service we have is pretty simple, and I want to complicate things a bit, to show how things would work in both the Dockerfile and the nix scenario.
Disclaimer:
Because I used to work for fly.io, I still benefit from an employee discount at the time of this writing: I don't have to pay for anything deployed there for now.
Now that our service is production-ready, it's time to deploy it somewhere.
There's a lot of ways to approach this: what we are going to do, though, is build a docker image. Or, I should say, an OCI image.
Now that our development environment is all set up, let's make something useful!
Creating the catscii
crate
From a VS Code window connected to our VM (as we just set up), let's make a new Rust project:
amos@miles:~$ cargo new catscii Created binary (application) `catscii` package
This time around, we're porting a solution from C++ to Rust and seeing how it feels, how it performs, and what we can learn about both languages by doing that.
Advent of Code gets harder and harder, and I'm not getting any smarter. Or any more free time. So, in order to close out this series anyway, I'm going to try and port other people's solutions from "language X" to Rust. That way, they already figured out the hard stuff, and we can just focus on the Rust bits!
Let's tackle the day 16 puzzle!
Parsing
The input looks like this:
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB Valve BB has flow rate=13; tunnels lead to valves CC, AA Valve CC has flow rate=2; tunnels lead to valves DD, BB Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE Valve EE has flow rate=3; tunnels lead to valves FF, DD Valve FF has flow rate=0; tunnels lead to valves EE, GG Valve GG has flow rate=0; tunnels lead to valves FF, HH Valve HH has flow rate=22; tunnel leads to valve GG Valve II has flow rate=0; tunnels lead to valves AA, JJ Valve JJ has flow rate=21; tunnel leads to valve II
The day 15 puzzle falls into the "math puzzle" territory more than "let's learn something new about Rust", but since several folks asked if I was going to continue... let's continue.
I like how the day 14 puzzle sounds, because I think it'll give me an opportunity to show off yet another way to have Rust embedded in a web page.
The day 13 puzzle needs a speech therapist.
???
...because it has an awful lisp!! Ahhhahahahhhh
Are you ok? What is.. what is going on with you?
Alright! The day 12 puzzle involves path finding, and it seems like a good time to lean more heavily on the WASM embeds I've set up for the previous parts.
It's a new day, it's a new advent of code puzzle.
In that one, we have to apparently cosplay as an IBM mainframe and just.. crunch them numbers. This doesn't look fun, and I can't think of a clever twist to make it fun, so let's try to make it short and sweet.
Parsing
Onwards! To the day 10 puzzle.
I don't see a way to make part 1 especially fun — so let's just get to it.
Parsing
The Advent of Code is not a sprint: it's a marathon: sometimes you've got to stop and smell the roses.
I... what? That's not.. have you done a marathon before?
In the day 8 problem, our input is a height map:
30373 25512 65332 33549 35390
This is a 5x5 grid, and every number denotes the height of a tree. For part 1, we must find out how many trees are visible from the outside of the grid.
The day 7 challenge talks about trees! File trees that is.
The temptation to solve it before starting to write this article so I don't look silly is high, but I'm explicitly not doing so, so that we can bang our collective heads against any walls at the same time, and see how we can get out of it! Trees are serious business!
Part 1
Today I am joining you from the relative discomfort of my living room (since my better half has commandeered the home office due to Way Too Many Calls) to tackle the day 6 challenge, which I'm excited about: maybe despite, maybe because of, the low-grade fever I'm under.
Part 1
Part 1
The day 5 challenge actually looks fun!
Our input looks like this:
[D] [N] [C] [Z] [M] [P] 1 2 3 move 1 from 2 to 1 move 3 from 1 to 3 move 2 from 2 to 1 move 1 from 1 to 2
Part 1
Let's tackle the day 4 challenge!
In this one, we get an input like this:
2-4,6-8 2-3,4-5 5-7,7-9 2-8,3-7 6-6,4-6 2-6,4-8
Part 1
I'm not sure where the day 3 challenge is going, because the problem statement for the first part is kinda convoluted.
Part 1
In the day 2 challenge, we're playing Rock Papers Scissors.
We're given a strategy guide like so:
A Y B X C Z
Two years ago, I did part of Advent of Code 2020 using the Rust language. It was a lot of fun, so let's try it again!
The problem statement
async_trait
's one weird type ascription trick
Now that I got the Log in with GitHub feature working, let's explore
what this would've looked like with the async_trait
crate.
Because I started accepting donations via GitHub Sponsors, and because donating at the "Silver" tier or above gives you advance access to articles and your name in the credits, I need to interface with the GitHub API the same way I do the Patreon API.
Now that my website is deployed as a container image, I wanted to give
nix a try. I'm still doing it the old-fashioned way right
now: with a Dockerfile
, running cargo
in a "builder" image, copying stuff
out of there into a slimmer image (that still has an Ubuntu base, even though
distroless images are a
thing now).
But why?
Disclaimer:
Although I no longer work for the company my website is hosted on, and this article is written in way that mentions neither my previous or current hosting provider: at the time of this writing, I don't pay for hosting.
HTTP does a pretty good job staying out of everyone's way.
If you're reading this article, there's a solid chance it was delivered to you over HTTP. Even if you're reading this from an RSS reader or something. And you didn't even have to think about it!
I don't mean to complain. Doing software engineering for a living is a situation of extreme privilege. But there's something to be said about how alienating it can be at times.
One could say I have a bit of an obsession with build times.
I believe having a "tight feedback loop" is extremely valuable: when I work on a large codebase, I want to be able to make small incremental changes and check very often that things are going as expected.
It happened when I least expected it.
Someone, somewhere (above me, presumably) made a decision. "From now on", they declared, "all our new stuff must be written in Rust".
Long story short: a couple of my articles got really popular on a bunch of sites, and someone, somewhere, went "well, let's see how much traffic that smart-ass can handle", and suddenly I was on the receiving end of a couple DDoS attacks.
Up until recently, hyper was my favorite Rust HTTP framework. It's low-level, but that gives you a lot of control over what happens.
As the popular saying goes, there are only two hard problems in computer science: caching, off-by-one errors, and getting a Rust job that isn't cryptocurrency-related.
I often write pieces that showcase how well Rust can work for you, and how it can let you build powerful abstractions, and prevents you from making a bunch of mistakes.
I still get excited about programming languages. But these days, it's not so much because of what they let me do, but rather what they don't let me do.
In the wake of Why is my Rust build so
slow?, developers from the mold
and
lld
linkers reached
out,
wondering why using their linker didn't make a big difference.
There's one thing that bothers me. In part 1, why are we using
hyper-staticfile
? Couldn't we just use file:///
URLs?
NO! No no no.
What?
WE WERE DONE!
Well... yes! But also no. We still shell out to a bunch of tools:
$ rg 'Command::new' src/commands/mod.rs 126: let variant = if let Ok(output) = run_command(Command::new("wslpath").arg("-m").arg("/")) { src/commands/cavif.rs 29: Command::new("cavif") src/commands/imagemagick.rs 25: Command::new(&self.bin) src/commands/cwebp.rs 25: Command::new("cwebp") src/commands/svgo.rs 25: Command::new("svgo")
I was a bit anxious about running our poppler meson build in CI, because it's the real test, you know? "Works on my machine" only goes so far, things have a tendency to break once you try to make them reproducible.
What's next? Well... poppler is the library Inkscape uses to import PDFs.
Yes, the name comes from Futurama.
I've recently come back to an older project of mine (that powers this website), and as I did some maintenance work: upgrade to newer crates, upgrade to a newer rustc, I noticed that my build was taking too damn long!
Writing Rust is pretty neat. But you know what's even neater? Continuously testing Rust, releasing Rust, and eventually, shipping Rust to production. And for that, we want more than plug-in for a code editor.
Has this ever happened to you?
You want to look at a JSON file in your terminal, so you pipe it into jq so you can look at it with colors and stuff.
So! Rust futures! Easy peasy lemon squeezy. Until it's not. So let's do the easy thing, and then instead of waiting for the hard thing to sneak up on us, we'll go for it intentionally.
Welcome back to the eighteenth and final part of "Making our own executable packer".
In the last article, we had
a lot of fun. We already had a "packer" executable, minipak
, which joined
together stage1
(a launcher), and a compressed version of whichever executable
we wanted to pack.
Here's a sentence I find myself saying several times a week:
...or we could just box it.
There's two remarkable things about this sentence.
withoutI'd like to think that my understanding of "async Rust" has increased over the past year or so. I'm 100% onboard with the basic principle: I would like to handle thousands of concurrent tasks using a handful of threads. That sounds great!
Welcome back!
In the last article, we
did foundational work on minipak
, our ELF packer.
It is now able to receive command-line arguments, environment variables, and
auxiliary vectors. It can parse those command-line arguments into a set of
options. It can make an ELF file smaller using the LZ4 compression
algorithm, and pack
it together with stage1
, our launcher.
And we're back!
In the last article, we thanked our old code and bade it adieu, for it did not spark joy. And then we made a new, solid foundation, on which we planned to actually make an executable packer.
You're still here! Fantastic.
I have good news, and bad news. The good news is, we're actually going to make an executable packer now!
Good morning, and welcome back to "how many executables can we run with our custom dynamic loader before things get really out of control".
weIt's time for the Day 14 problem!
After the hassle that was Day 13, I hope this time we'll have a relatively chill time. And, at least for Part 1, that is true.
In the Day 13 problem, we're trying to take the bus.
Our input looks like this:
939 7,13,x,x,59,x,31,19
The first line indicates the earliest minute we can leave from the bus terminal, and the second line indicates the "identifier" of the buses that are active.
Time for the Day 12 problem!
In this problem, we have a ship. And we have navigation instructions:
- Action
N
means to movenorth
by the given value. - Action
S
means to movesouth
by the given value. - Action
E
means to moveeast
by the given value. - Action
W
means to movewest
by the given value. - Action
L
means to turnleft
the given number of degrees. - Action
R
means to turnright
the given number of degrees. - Action
F
means to moveforward
by the given value in the direction the ship is currently facing.
Another day, another problem.
This time the problem looks suspiciously like Conway's Game of Life, or, I guess, any old Cellular automaton.
Day, 10! Day, 10!
Okay, Day 10.
Again, the problem statement is very confusing - but what it all boils down to is this. We have a list of numbers:
16 10 15 5 1 11 7 19 6 12 4
Day 9's problem statement is convoluted - the "ah maybe that's why I don't usually do Advent of Code" kind of convoluted, but let's give it a go anyway.
Time for another Advent of Code 2020 problem!
That one sounds like it's going to be fun. Our input is pretty much assembly, like this:
nop +0 acc +1 jmp +4 acc +3 jmp -3 acc -99 acc +1 jmp -4 acc +6
Another day, another Advent of Code 2020 problem.
That one seems fun! For some nerdy values of fun.
Our input is a set of rules:
light red bags contain 1 bright white bag, 2 muted yellow bags. dark orange bags contain 3 bright white bags, 4 muted yellow bags. bright white bags contain 1 shiny gold bag. muted yellow bags contain 2 shiny gold bags, 9 faded blue bags. shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags. dark olive bags contain 3 faded blue bags, 4 dotted black bags. vibrant plum bags contain 5 faded blue bags, 6 dotted black bags. faded blue bags contain no other bags. dotted black bags contain no other bags.
The end of Advent of Code 2020 is fast approaching, and we're nowhere near done. Time to do Day 6!
The problem statement here is a little contrived, as uh, as the days that came before it, but that won't stop us.
Time for another day of Advent of Code 2020.
For Day 5, we're going to have to do...
Let me guess: more parsing?
It's time for Day 4 of the Advent of Code 2020!
Now, I've already had a look at the problem statement, at least for part 1, and I'm not particularly excited.
Hello all, and welcome back to Advent of Code 2020, featuring Cool Bear.
Hey y'all!
Let's get right to it.
The problem statement for Day 3 is as follows: we're given a map, that looks like this:
..##....... #...#...#.. .#....#..#. ..#.#...#.# .#...##..#. ..#.##..... .#.#.#....# .#........# #.##...#... #...##....# .#..#...#.#
Day 2, Day 2! Woo!
The Advent of Code 2020, Day 2 problem talks about passwords. Sounds familiar.
Basically, our input looks like this:
1-3 a: abcde 1-3 b: cdefg 2-9 c: ccccccccc
I was not planning on doing anything specific this December, but a lot of folks around me (on Twitter, at work) have chosen this Advent of Code to pick up Rust, and I've got big FOMO energy, so, let's see where this goes.
The Nature weekly journal of science was first published in 1869. And after one and a half century, it has finally completed one cycle of carcinization, by publishing an article about the Rust programming language.
Good morning! It is still 2020, and the world is literally on fire, so I guess we could all use a distraction.
This article continues the tradition of me getting shamelessly nerd-sniped - once by Pascal about small strings, then again by a twitch viewer about Rust enum sizes.
During a recent Rust Q&A Session on my twitch
channel, someone asked a question that
seemed simple: why are small string types, like SmartString
or SmolStr
,
the same size as String
, but small vec types, like SmallVec
, are larger
than Vec
?
Learning Rust is... an experience. An emotional journey. I've rarely been more frustrated than in my first few months of trying to learn Rust.
willAs I've said before, I'm working on a book about lifetimes. Or maybe it's just a long series - I haven't decided the specifics yet. Like every one of my series/book things, it's long, and it starts you off way in the periphery of the subject, and takes a lot of detours to get there.
I used to be afraid of async Rust. It's easy to get into trouble!
But thanks to the work done by the whole community, async Rust is getting easier to use every week. One project I think is doing particularly great work in this area is async-std.
I write a ton of articles about rust. And in those articles, the main focus is about writing Rust code that compiles. Once it compiles, well, we're basically in the clear! Especially if it compiles to a single executable, that's made up entirely of Rust code.
notI started experimenting with asynchronous Rust code back when futures 0.1
was all we had - before async/await
. I was a Rust baby then (I'm at least
a toddler now), so I quickly drowned in a sea of .and_then
, .map_err
and Either<A, B>
.
Hey everyone!
This article is brought to you by a shameless nerd snipe, courtesy of Pascal.
In case you've blocked Twitter for your own good, this reads:
Since I write a lot of articles about Rust, I tend to get a lot
of questions about specific crates: "Amos, what do you think of oauth2-simd
?
Is it better than openid-sse4
? I think the latter has a lot of boilerplate."
I've been banging the same drum for years: APIs must be carefully designed.
This statement doesn't resonate the same way with everyone. In order to really understand what I mean by "careful API design", one has to have experienced both ends of the spectrum.
Hi everyone. Has it been two months since I last posted something? Yes it has!
That seems like a nice round duration, so let's break the silence with a few announcements.
I have a new website
Welcome back and thanks for joining us for the reads notes... the thirteenth installment of our series on ELF files, what they are, what they can do, what does the dynamic linker do to them, and how can we do it ourselves.
In Part 11, we spent some time clarifying mechanisms we had previously glossed over: how variables and functions from other ELF objects were accessed at runtime.
In our last installment of
"Making our own executable packer", we did some code cleanups. We got rid of
a bunch of unsafe
code, and found a way to represent memory-mapped data
structures safely.
Welcome back to the "Making our own executable packer" series, where digressions are our bread and butter.
Last time, we implemented indirect functions in a no-libc C program. Of course, we got lost on the way and accidentally implemented a couple of useful elk-powered GDB functions - with only the minimal required amount of Python code.
In the last article, we cleaned up our dynamic linker a little. We even
implemented the Dynamic
relocation.
But it's still pretty far away from running real-world applications.
There's a question that always comes up when people pick up the
Rust programming language: why are there two
string types? Why is there String
, and &str
?
Let's pick up where we left off: we had just taught elk
to load
not only an executable, but also its dependencies, and then their
dependencies as well.
In order to increase fluency in a programming language, one has to read a lot of it.
But how can you read a lot of it if you don't know what it means?
Up until now, we've been loading a single ELF file, and there wasn't much
structure to how we did it: everyhing just kinda happened in main
, in no
particular order.
In our last article, we managed to load and execute a PIE (position-independent executable) compiled from the following code:
; in `samples/hello-pie.asm` global _start section .text _start: mov rdi, 1 ; stdout fd lea rsi, [rel msg] mov rdx, 9 ; 8 chars + newline mov rax, 1 ; write syscall syscall xor rdi, rdi ; return code 0 mov rax, 60 ; exit syscall syscall section .data msg: db "hi there", 10
The last article, Position-independent code, was a mess. But who could blame us? We looked at the world, and found it to be a chaotic and seemingly nonsensical place. So, in order to blend in, we had to let go of a little bit of sanity.
In the last article, we found where code was hiding in our samples/hello
executable, by disassembling the whole file and then looking for syscalls.
In part 1, we've looked at three executables:
sample
, an assembly program that prints "hi there" using thewrite
system call.entry_point
, a C program that prints the address ofmain
usingprintf
- The
/bin/true
executable, probably also a C program (because it's part of GNU coreutils), and which just exits with code 0.
Executables have been fascinating to me ever since I discovered, as a kid,
that they were just files. If you renamed a .exe
to something else, you
could open it in notepad! And if you renamed something else to a .exe
,
you'd get a neat error dialog.
So. Serializing IPv4 packets. Easy? Well, not exactly.
IPv4 was annoying to parse, because we had 3-bit integers, and 13-bit integers, and who knows what else. Serializing it is going to be exactly the same.
Alright. ALRIGHT. I know, we're all excited, but let's think about what we're doing again.
So we've managed to look at real network traffic and parse it completely. We've also taken some ICMP packets, parsed them, and then serialized them right back and we got the exact same result.
In the last part, we've finally parsed some IPv4 packets. We even found a way to filter only IPv4 packets that contain ICMP packets.
Before we move on to parsing more of our raw packets, I want to take some time to improve our error handling strategy.
Currently, the ersatz
codebase contains a mix of Result<T, E>
, and some
methods that panic, like unwrap()
and expect()
.
Now that we've found the best way to find the "default network interface"... what can we do with that interface?
Okay, I lied.
I'm deciding - right this instant - that using wmic is cheating too. Oh, it was fair game when we were learning about Windows, but we're past that now.
Let's set aside our sup
project for a while.
Don't get me wrong - it's a perfectly fine project, and, were we simply rewriting "ping" for Windows in Rust, we could (almost) stop there.
Our ping API is simple, but it's also very limited:
pub fn ping(dest: ipv4::Addr) -> Result<(), String> // called as: ping(ipv4::Addr([8, 8, 8, 8])).unwrap();
We've just spent a lot of time abstracting over LoadLibrary, but we still have all the gory details of the Win32 ICMP API straight in our main.rs file! That won't do.
It's refactor time!
Our complete program is now about a hundred lines, counting blank lines (see the end of part 3 for a complete listing).
It's time to make sup
, our own take on ping
, use the Win32 APIs to send
an ICMP echo. Earlier we discovered that Windows's ping.exe
used
IcmpSendEcho2Ex
. But for our purposes, the simpler IcmpSendEcho
will do
just fine.
So, how does ping.exe
actually send a ping? It seems unrealistic that
ping.exe
itself implements all the protocols involved in sending a ping.
So it must be calling some sort of library. Also, since it ends up
talking to the outside world via a NIC (network interface controller),
the kernel is probably involved at some point.
It feels like an eternity since I've started using Rust, and yet I remember vividly what it felt like to bang my head against the borrow checker for the first few times.
So far, we've seen many ways to read a file from different programming languages, we've learned about syscalls, how to make those from assembly, then we've learned about memory mapping, virtual address spaces, and generally some of the mechanisms in which userland and the kernel interact.
Everybody knows how to use files. You just open up File Explorer, the Finder, or a File Manager, and bam - it's chock-full of files. There's folders and files as far as the eye can see. It's a genuine filapalooza. I have never once heard someone complain there were not enough files on their computer.
A while back, I asked on Twitter what people found confusing in Rust, and one of the top topics was "how the module system maps to files".
In my previous article, I said I needed to stop thinking of Rust generics as Java generics, because in Rust, generic types are erased.
Go back to the homepage.