a (negative) review of rust
rust vs ruby
rust is a very "safe" language, and
there are cool papers that prove it.
as a consequence, I'm sure it's very
good for group development and so on.
if you're someone like me without
industry experience <insert trademark sign>,
that doesn't really matter too much.
what matters a lot to me is how fun
a language is -- and among the many
blogposts about rust, there aren't many
that assess this quality (and if there
are, the author convinces themself that
they think good performance is their idea
of "fun", or something like that).
so how fun (subjectively) is rust?
one of the languages that rusts cites as
an influence is ruby, a language
that was explicitly designed to be fun.
overall, ruby's philosophy focuses on more
soft, human ideas of usability than formal
proofs of "correctness".
the main (only?) influence ruby had on rust
has to do with rust's closure syntax:
let my_closure = |i| { i + 1 };
this looks close enough to ruby's idea
of blocks:
my_closure = Proc.new {|i| i + 1 }
that's the extent of the ruby influence though,
unless you count the snake_case. unfortunately,
rust forgot to `use fun`. ruby's syntax is
closely tied to its philosophy, so the claim
that rust was actually influenced
by ruby is kind of tenuous. programming
in rust feels a lot like pulling teeth, and
knowing that your program won't unexpectedly
crash as often doesn't make that feel any
better for a solo programmer who's just
working on a hobby project.
I should mention at this point that I'm
not claiming rust owes me anything, because
I know the language was designed for bigger
team projects where reliability is crucial.
really, this blogpost is an exploration of
the factors that affect the experience
of using a language. I know lots of people
like rust, and the crab mascot is pretty cute.
once again this is just an exploration,
not an attack.
tangent about design
anyway, I have lots of questions and ideas
about what makes a programming language
good and bad. as far as the criterion of
fun goes, one determining factor (I think?)
is whether or not the language is designed
by a committee rather than by a "benevolent
dictator for life." examples of the former
would be c, rust, c++, golang, and java;
examples of the latter inclue ruby
(matz), clojure (rich hickey),
and perl (larry wall).
while there's obviously issues with giving
individuals credit for programming languages
that they invented but that ultimately grew due
to the contributions of the open-source community,
a committee cannot give a language style.
a unified taste or "feel" is best accomplished
through the lens of a single person rather than
a group that tries to anticipate the desires
of an imaginary audience.
but _why_ don't you think rust is fun???
all of this hatred is still kind of vague, though.
why in specific do I dislike rust? it will be
hard to pinpoint exactly what about it makes me
feel bad, because humans haven't had programming
languages for most of history -- so there aren't
ways of describing how a using language feels the
way there are words to describe smells, tastes,
or even more established experiences such as
riding a sailboat. basically, I'm still waiting
for a cassettepunk utopia to be established.
syntax is everything
here's a rust version of the classic 'hello world':
fn main() {
println!("Hello world!");
}
this isn't that bad. compare it to other systems
programming languages:
haskell
main :: IO ()
main = putStrLn "Hello world!"
java (*scream*)
public class Hello {
public static void main(String[] args) {
System.out.println("Hello world!");
}
}
good old c
#include <stdio.h>
int main() {
printf("Hello world!\n");
return 0;
}
the rust 'hello world' has possibly the least amount
of syntactic noise out of these examples, although
this comes at the cost of having to infer things like
types and visibility (although that's not necessarily bad).
the thing is, rust programs get a lot more complicated
than that. in a language like java, simple programs can
look really verbose, but that's just about as verbose
as even the really complicated programs end up.
this is partially because java doesn't have advanced
features like rust or haskell. however, haskell still
manages to deal with advanced things like types far
better than rust does. here's an example from the
Control.Lens package, which is gigantic and allows for
some wild functional metaprogramming (comments have
been stripped out):
class
( Choice p, Corepresentable p, Comonad (Corep p), Traversable (Corep p)
, Strong p, Representable p, Monad (Rep p), MonadFix (Rep p), Distributive (Rep p)
, Costrong p, ArrowLoop p, ArrowApply p, ArrowChoice p, Closed p
) => Conjoined p where
distrib :: Functor f => p a b -> p (f a) (f b)
distrib = tabulate . collect . sieve
{-# INLINE distrib #-}
conjoined :: ((p ~ (->)) => q (a -> b) r) -> q (p a b) r -> q (p a b) r
conjoined _ r = r
{-# INLINE conjoined #-}
the types themselves are a little cryptic without
context, and the typeclass constraints are a bit long.
but overall, there's not much visual noise going
on that distracts from the main point: a typeclass
is being defined, it has lots of dependencies,
and the functions are just composed of a few other
functions and they get inlined. what's also important
is that the big types and type dependencies don't
accumulate and get carried with every time you use
a `Conjoined p`, because said types can filled in
and reduced. a good way to compare this to rust is
thinking about flat versus nested syntax,
where haskell has pretty flat types and rust's types
often nest a lot more.
oh no
next, let's look at some complicated rust code. how
bad can it be, right? rust is a modern programming
language, and it even uses type inference, so a
complex program should look like the haskell example
... right? we'll be looking at tokio, a crate
whose description ("A runtime for writing reliable,
asynchronous, and slim applications with the Rust
programming language") is like the motto of some
technology company that doesn't get anything done and
is actually a front for money laundering.
without further ado:
impl<T: AsRef<[u8]> + Unpin> AsyncSeek for io::Cursor<T> {
fn start_seek(
mut self: Pin<&mut Self>,
_: &mut Context<'_>,
pos: SeekFrom,
) -> Poll<io::Result<()>> {
Poll::Ready(io::Seek::seek(&mut *self, pos).map(drop))
}
fn poll_complete(self: Pin<&mut Self>, _: &mut Context<'_>)
-> Poll<io::Result<u64>> {
Poll::Ready(Ok(self.get_mut().position()))
}
}
welcome to chaos! this would make some pretty cool
ascii art, but as code there's so much visual noise. much of
what makes writing code fun gets ruined in rust when
you have to explicitly write out so many type constraints
and memory constraints that it feels like you're trying
to write out a legal agreement with a demon who's very
intent on finding a loophole that allows your soul to be
stolen. if you've ever used rust, you may have had the sad
experience of adding a field of type `&str` to a struct that's
widely used throughout your codebase, only to spend the
next five hours putting a little `<'a>` everywhere.
furthermore, you end up getting really weird shapes of code
nesting since rust uses parentheses for function invocations
and enum construction, and then it also uses
method chaining a lot of the time. essentially, it's trying
to pull off functional programming tricks but still dressing
itself like c or java rather than lisp or haskell or ocaml.
and thirdly: how much of this keysmash-esque information is
actually necessary in two one-liner functions? rust may
be good at eliminating boilerplate functionality, but
it only worsens boilerplate syntax.
as we have seen, a rust program can grow into a monstrous
syntactic fractal that interferes with comprehension.
even the "glue" elements of the syntax -- braces, brackets,
arrows, etc -- tag along on this fractal train, and
they get everywhere. first they're like little
warts, and then they explode like popcorn kernels when
the program gets even bigger. eventually the fractal starfishy
arms of trait/lifetime/borrow constraints grow so out-of-control
that these barely meaningful syntactical bits become like
teeny tiny bits of glittery sand that stick to the tear-covered
surfaces of the annotations, now curling in alien nautiloid
shapes across the sky. the abstractions unfurl strange fronds,
and a primal instinct inside you tells you to run away
-- is it filter-feeding? not-quite-organic processes alight
within these structures, yet all that crustaceous glitter
obscures it from your sight. there's so much glitter that it
rains down now, falling festively upon the ground like sand. you
can run your hands through it, and you quickly give in to
that sudden urge to trace out pictures of crabs and other
unspeakable entities; then the glitter is deep enough that you can
make "snow" angels (ignore the rumbling noise), so long as
you lie in a vulnerable position, peripheral vision obscured
by the rising tides of sparkles. a few specks of glitter even
land in your mouth, but you safely cover your face because
they taste like phenolphthalein.
--
generally, more complicated syntax discourages the
programmer from using features that require such syntax,
so while it's certainly easy to make lots of .unwrap()
calls, doing the functional programming that the rust
developers have taken the pain to implement comes at
a cost to your own happiness -- and it shouldn't be that way.
I also think rust's borrow checker is no fun, but I don't
need to explain that at all.