I didn’t explain the unsafe block before. Everything inside
unsafe{} is unsafe code. This particular code is unsafe because it
accesses a memory address directly. Wrapping it in an unsafe block
tells Rust “okay, I checked and I promise this code is actually doing
the right thing and won’t blow anything up”.

In the VGA buffer, each character is represented by 2 bytes (so a
u16). The lower 8 bits are the ASCII character, and the upper 8 bits
are the foreground and background colour (4 bits each). Color here
is an enum so that I can refer to Red or Green directly.

I found this part pretty approachable and it didn’t take too long.
Which isn’t to say that I didn’t have problems! I had SO MANY
PROBLEMS. Most of my problems were to do with arrays and string and
iterating over strings. Here’s some code that caused me much pain:

This code looks simple! It is a lie.
Friends. Here were some questions that I needed to ask to write this code.

How do I turn a string into a byte array? (as_bytes())

What is the type of a byte array? (&[u8])

How do I iterate over a byte array? (+ “it still doesn’t work!”, 4 times)

Also, what is this super::core::slice::iter business? This brings us
to a fairly long digression, and an important point

Why you can’t write a kernel in Python

So you want to write an operating system, let’s say for x86. You need
to write this in a programming language!

Can you write your operating system in Python (using CPython, say)?
You cannot. This is not being curmudgeonly! It is actually just not
possible.

What happens when you write print "Hello!" in Python?

Well, many things happen. But the last thing that happens is that
the CPython interpreter will do something like printf("Hello"). And
you might think, well, maybe I could link against the code for
printf somehow!

But what printf does is it calls the write() system call. The
write() system call is implemented IN YOUR KERNEL.

OH WAIT YOU DON’T HAVE A KERNEL YET. YOU ARE WRITING ONE.

This also means that you can’t write a kernel as a “normal” C program
which includes C libraries. Any C libraries. All C libraries for Linux
are built on top of some version of libc, which makes calls to the
Linux kernel! So if you’re writing a kernel, this doesn’t work.

Why you can write a kernel in Rust

Writing Rust code has many of the same problems, of course! By
default, if you compile a Rust program with a print statement, it will
call your kernel’s equivalent to write.

But! Unlike with Python, you can put #[no_std] at the beginning of
your Rust program.

You lose a lot! You can no longer

allocate memory

do threading

print anything

many many more things

It’s still totally fine to define functions and make calculations,
though. And you can of course define your own functions to allocate
memory.

You also lose things like Rust’s iterators, which is sad!

rust-core

rust-core is “a standard
library for Rust with freestanding support”. What this means is that
if you’re writing an OS, rust-core will provide you with all kinds
of helpful data structures and functions that you lost when you wrote
#[no_std].

I found using this library pretty confusing, but the author hangs out
in IRC all the time and was really friendly to me, so it wasn’t a huge
problem.

So back to super::core::slice::iter! This says “iterate over this
using an iteration function from rust-core“

Step 4: keyboard interrupts!

So it took me a few days to learn how to print because I needed
to learn about freestanding mode and get confused about rust-core and
at the same time I didn’t really understand Rust’s types very well.

Step 5: malloc!

After I’d done that, I thought it might be fun to be able to allocate
memory.

You may be surprised at this point. We have printed strings! We have
made our keyboard work! Didn’t we need to allocate memory? Isn’t
that… important?

It turns out that you can get away without doing it pretty easily!
Rust would automatically create variables on the stack for me, so I
could use local variables. And for anything else I could use global
variables, and the space for those was laid out at compile time.

But allocating memory seemed like a fun exercise. To allocate
something on the heap in Rust, you can do

let a = ~2

This creates a pointer to a 2 on the heap. Of course, we talked
before about how there is no malloc! So I wrote one, and then made
sure that Rust knew about it.

This weird-looking #[lang = "exchange_malloc"] bit means “Code like
let x = ~2 is now allowed to work”. It requires there to be an
implementation of malloc, which I wrote. It also needs implements of
realloc and free, but I left those blank :)

Before seeing that, Rust would not compile code that allocated memory.

I think this language feature gating is really cool: it means that you
can write Rust programs that can allocate memory, but not do
threading. Or that can do hardly anything at all!

I need to get up now.

Next up: running problems! AND SOMETHING IS ERASING MY PROGRAM WHILE
IT IS RUNNING.