Hello and welcome to another edition of The Monthly Oxide, a newsletter where you get to learn a bit more about Rust and get some links to some interesting articles and projects I found interesting this month. This month we’ll learn some interesting concepts from the compiler weird_expr.rs
test file that can give you a deeper understanding of the language, even if you really wouldn’t write code that way. We’ll also include the usual articles and projects so let’s get started!
Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo
It’s important to note that what we’ll be looking at (this file here), isn’t idiomatic or even useful Rust. Not by a long shot. However, it is grammatically correct. These tests exist because it would indicate something has gone horribly wrong with the compiler. They utilize interesting corner cases of the language so they can compile. We’re gonna look at two from the file that I think will give you some interesting insight into the language: zombie_jesus
and r#match
.
We’ll start with r#match
since it’s near and dear to my heart
fn r#match() {
let val = match match match match match () {
() => ()
} {
() => ()
} {
() => ()
} {
() => ()
} {
() => ()
};
assert_eq!(val, ());
}
Let’s rewrite this as an equivalent program that makes it easier to understand
fn r#match() {
let intermediate_val_1 = match () {
() => ()
};
let intermediate_val_2 = match intermediate_val_1 {
() => ()
};
let intermediate_val_3 = match intermediate_val_2 {
() => ()
};
let intermediate_val_4 = match intermediate_val_3 {
() => ()
};
let val= match intermediate_val_4 {
() => ()
};
assert_eq!(val, ());
}
At each step we match on the value, check what to do with it when it matches a pattern and return a value. In this case the test only uses ()
but you could easily do this with strings or numbers. In fact here’s another version that hopefully makes it more clear what the transformation looks like:
fn r#match() {
let val = match match match match match 1 {
1 => "Good Dog"
_ => "Bad Dog"
} {
"Good Dog" => Some(10),
"Bad Dog" => None,
_ => unreachable!(),
} {
Some(10) => "Bar",
None => "Baz",
_ => unreachable!(),
} {
"Bar" => 30,
"Baz" => 15,
} {
15 => 42,
30 => 89,
_ => unreachable!(),
};
assert_eq!(val, 89);
}
You can call match
on a match
because it takes an expression or a value as input. As long as the expression evaluates to some value it can match on then it works. So you can arbitrarily nest match
expressions like this so long as the types all line up. Pretty wild huh? I really love that Rust is an expression based language because it can let you do some pretty wild things or fairly normal. Consider the vec![]
macro. It just expands out to this:
let x = vec![1,2,3]; // is the same as
let x = {
let mut temp = Vec::new();
temp.push(1);
temp.push(2);
temp.push(3);
temp
};
We create a mutable Vec
add some values and the final value of the expression block (that’s the curly braces) which is the return value of the expression, is then assigned to x! Now we have a Vec
that’s immutable with those 3 values inside it. Pretty neat! You can nest expressions in interesting ways as well. Rust is known for not having a do while loop construct but that’s a lie it does have one:
let x = 0;
while {
println!("This will only print once!")
x += 1;
x == 0
}{}
As you can see we have an expression that does some things, increments the count and most importantly returns a boolean value. The loop doesn’t care as long as it type checks and gets a boolean. It then skips the block where you’d normally put things for a loop because we don’t want to do things twice every loop!
My point about this test that I want to emphasize is that expressions and expression blocks are powerful and you can try them in more places than you’d expect. Give it a shot some time and see if you can get some weird things to work. You might find your code ends up being more readable than you’d expect.
Okay let’s take a look at zombie_jesus:
fn zombiejesus() {
loop {
while (return) {
if (return) {
match (return) {
1 => {
if (return) {
return
} else {
return
}
}
_ => { return }
};
} else if (return) {
return;
}
}
if (return) { break; }
}
}
I know what you’re thinking, why does the if and while statements have parentheses? This isn’t Java. Well we’ll get to that but first what’s up with all the returns? Why do they work? Well first let’s learn about the never type or !.
The never type (while unstable) is a type that means “This value can never be constructed”. You can even do this by making an enum with no variants as you can’t initialize an enum without any variants. That means enum Bukka {}
can never be instantiated. These have there uses like indicating that the program should never ever end fn main() -> !
for instance. The important part though is that because it can never be constructed, it can be any type, because you will never be able to make it and so for all the compiler cares this is valid and type checks.
What does this have to do with return? Well what happens when you call return? You just return a value and exit the program right then and there. In Rust a return without a value just returns ()
and a function without a return value implicitly returns ().
However, looking at the code in places that should be expecting booleans a return statement is there! It’s not a boolean. This is true, but we never need to construct a value there because we exit the function before we ever need a value. What Rust does here is treat the return like the never type, since we never need it, and causes it to type check for whatever is needed there. For instance these will compile just fine:
struct Foo;
struct Oop;
let bar: Foo = return;
let baz: Oop = return;
So most of the code in zombie_jesus
never runs. It looks confusing, but it just exits at the first call to return. Neat huh?
Articles
Structural Typing in Rust - I love me some good type theory posts that don’t feel like a boring math lecture. Look I failed my multi variable calc class so hard I just cried for 2 hours on my final, I don’t need more math. I want just good articles and this is one.
The Plan for the Rust 2021 Edition - I can’t believe it’s been almost 3 years since the last edition and there’s so much good stuff coming in this one, like disjoint captures in closures! If you haven’t heard about it yet, give it a read. There’s a lot of fun changes coming down the pipeline.
Projects
Parcel has a new version coming out that’s currently in beta and the speed gains are something else. Definitely one of those “Rewrite It In Rust” things that brought significant gains. Look please, I just want to use anything that doesn’t make me suffer anytime I try to do web dev because the tools are so slow or break on me.
It’s been around for a bit, but I still love once_cell for when you need to intialize a global value at runtime without using the lazy_static macro