add crystal post, small style tweaks
This commit is contained in:
217
content/blog/crystal-a-disappointing-story-of-hope.md
Normal file
217
content/blog/crystal-a-disappointing-story-of-hope.md
Normal file
@@ -0,0 +1,217 @@
|
||||
---
|
||||
title: 'Crystal: A Disappointing Story of Hope'
|
||||
description:
|
||||
I had a lot of hope for Crystal, but it wasn't as great as I thought it
|
||||
would be
|
||||
image:
|
||||
src: /images/crystal-a-disappointing-story-of-hope.webp
|
||||
alt: 'Crystal: A Disappointing Story of Hope'
|
||||
date: 2025-11-03T00:00
|
||||
tags:
|
||||
- crystal
|
||||
- programming
|
||||
---
|
||||
|
||||
I really wanted to like Crystal; the idea is amazing. “Imagine Ruby’s syntax
|
||||
with a static type system, and a compiled executable”, that could give you an
|
||||
outstanding first class developer expeirence, but in reality, it's not as simple
|
||||
as that, and it never is.
|
||||
|
||||
Crystal is a language I’ve been following and interested in for quite some time
|
||||
now. Crystal’s core idea has always really interested me. When I first heard of
|
||||
Crystal, I was getting into Ruby, and I really like its philosophy of “Ruby but
|
||||
statically typed and compiled”, ideally providing a more comprehensive developer
|
||||
experience, a faster runtime, and more portability since executables can be
|
||||
statically linked. I really wanted to like it, I really did, but its story isn't
|
||||
that of an underrated language, but an underwhelming one.
|
||||
|
||||
<!-- more -->
|
||||
|
||||
The syntax of crystal is nice, it’s basically Ruby but with a static type
|
||||
system, and I like Ruby’s syntax, and I like static types, it typically allows
|
||||
for a much better developer experience and you dont end up with
|
||||
[md5('240610708') equaling md5('QNKCDZO')](https://stackoverflow.com/questions/22140204/why-md5240610708-is-equal-to-md5qnkcdzo)
|
||||
due to values being cast to superfluous types for no good reason. You can also
|
||||
get better IntelliSense, usually, and often type errors and other things before
|
||||
runtime. Despite the promising features, playing around with Crystal quickly
|
||||
revealed a different story.
|
||||
|
||||
## Why Crystal failed me
|
||||
|
||||
### Tooling Failure
|
||||
|
||||
Crystal’s dev experience is less peachy than you would expect, given everything
|
||||
I’ve told you so far. I write most of my code in VSCode, for lots of reasons,
|
||||
since it’s just a web app means I can literally run a web server on a container
|
||||
and have an instant dev environment available from anywhere as long as I have an
|
||||
internet connection (and preferably more than 10” of screen). However, Crystal’s
|
||||
tooling negates most of its usefulness.
|
||||
|
||||
For anything that matters, Crystal's support in VSCode is terrible, and this is
|
||||
**with the crystal-lang-tools VSCode extension installed**. Without it, there is
|
||||
no native support for Crystal _at all_, which isn’t terribly surprising if I’m
|
||||
being honest. The crystal tooling seems to be largely community-driven, which
|
||||
can be a good thing, but the vscode-crystal-lang extension on their GitHub
|
||||
organization has almost no activity, with the latest code change being 3 months
|
||||
ago and the commit before that being 9 months old at the time of writing. This
|
||||
lackluster tooling extends into its LSP as well.
|
||||
|
||||
### LSP Limitations
|
||||
|
||||
The LSP often gives you outdated errors, and it starts to give me headaches
|
||||
after using it for any period of time. Its hints on hover are also not amazing,
|
||||
they are slow to appear, and 99% of the time, it gives you no more useful
|
||||
information than the type of the variable, in fact, most of the time, it gives
|
||||
you no hints whatsoever, and it makes you wonder if the problem is the LSP being
|
||||
unbelievably slow, or if the LSP _just sucks_.
|
||||
|
||||

|
||||
|
||||
There is **no autocomplete whatsoever**; you won't get recommendations for
|
||||
functions ever, not even built-in functions, _nothing_. The syntax highlighting
|
||||
is often lacking, and once again, it has no idea of functions that have been
|
||||
registered, leaving them un-highlighted. The only things that get highlighted
|
||||
are primitives and reserved keywords, aka basic functions, though you just have
|
||||
to know they exist without autocomplete. Which is definitely possible,
|
||||
autocomplete is not necessary, that’s the whole point of writing code on paper
|
||||
is to prove you _can_ write code without your nice IDE sugar and spice, but
|
||||
writing code on paper is generally an exercise to prove you _can_ do it. Imagine
|
||||
writing code like that _forever_; that’s what writing Crystal is like. In the
|
||||
modern development ecosystem, this is unthinkable for many devs.
|
||||
|
||||
Crystal has an ERB replacement that is nice to use and is nearly identical to
|
||||
ERB. However, while _sometimes_ it may be nice to use, _it often pains me to
|
||||
write_. Crystal tags in ECR files are not parsed or highlighted or anything
|
||||
really, and they often **break syntax highlighting of the entire file**, with no
|
||||
option for recourse other than to just give up or do something completely
|
||||
differently.
|
||||
|
||||

|
||||
|
||||
Here, I'm putting a crystal tag in a style tag, and that just breaks everything.
|
||||
The reason for putting a crystal tag in a style tag is to embed the CSS for the
|
||||
page, so there is no FOUC. The solution would be to print the entire <style> tag
|
||||
in the crystal string that it will output, or to just use an external CSS file
|
||||
like a sane person, maybe. What's more upsetting is how decent, just _basic_
|
||||
HTML highlight looks.
|
||||
|
||||

|
||||
|
||||
However, since ECR is nearly identical to ERB, you can rely on the mature and
|
||||
actually decent tooling for ECR files. For example, here’s the same example I
|
||||
have above, but I tell VSCode that ECR files are actually just ERB files and to
|
||||
highlight them like so:
|
||||
|
||||

|
||||
|
||||
Using crystal highlighting often gives you a better experience than the Crystal
|
||||
lang extension gives you, but unfortunately, it can't fix the terrible LSP that
|
||||
Crystal gives you, and highlighting Crystal code outside of ECR files as Ruby is
|
||||
not a great experience either.
|
||||
|
||||
Furthermore, Crystal’s compile times are a bit underwhelming, and if you use a
|
||||
watch script to increase the development speed and use of Crystal, you’ll be
|
||||
waiting a hot second for Crystal to recompile, even when in dev mode. This
|
||||
further worsens the usefulness of Crystal as a web server-focused language. I’m
|
||||
constantly looking for languages that would be ideal as a web server language,
|
||||
that provides an amazing DX, a good type system, and fast iteration. JavaScript
|
||||
really seems to be the best option as far as I can tell. While JavaScript has
|
||||
its pros, it also has plenty of cons. The attitude in the JavaScript ecosystem
|
||||
is to use someone else's wheel, but in JavaScript's case we have 120,000 wheels
|
||||
that all depend on each other. In my experience, the overreliance on JavaScript
|
||||
makes for a really bloated server executable and an unpleasant client-side
|
||||
experience.
|
||||
|
||||
### Syntactical decisions
|
||||
|
||||
Crystal is meant to be a very close analogue to Ruby, but many of its syntax
|
||||
decisions are frustrating, to put it lightly. **Crystal does not support for
|
||||
loops**, and attempting to write one will throw confusing and unhelpful errors.
|
||||
What's worse is that the **Crystal devs know this**: In their article
|
||||
[Crystal for Rubyists](https://crystal-lang.org/reference/1.16/crystal_for_rubyists/index.html),
|
||||
they have this section about for loops:
|
||||
|
||||

|
||||
|
||||
This is something I absolutely despise seeing in languages, removing features
|
||||
for no good reason; they even know this and give you a solution to their poorly
|
||||
made decisions. Instead, if you want to loop 5 times, your code might look like
|
||||
one of these:
|
||||
|
||||
```crystal
|
||||
|
||||
(0..4).each { |i| puts "Hello, world #{i}" }
|
||||
|
||||
(0..4).each do |i|
|
||||
puts "Hello, world #{i}"
|
||||
end
|
||||
|
||||
5.times do |i|
|
||||
puts "Hello, world #{i}"
|
||||
end
|
||||
```
|
||||
|
||||
I don’t hate this syntax, and I even quite like the `5.times` syntax, which
|
||||
gives you a plain English explanation of what the code is doing from the code
|
||||
itself, but why remove for loops? It just doesn’t make sense to me, especially
|
||||
since Ruby already has ranges. Writing for loops would be a breeze for anyone
|
||||
familiar with Ruby or Rust, or the dozens of other languages that share roughly
|
||||
the same for loop syntax.
|
||||
|
||||
While the Crystal team encourages `Enumerable#each` for a more idiomatic
|
||||
approach aligned with functional programming, the complete removal of `for`
|
||||
loops feels like an unnecessary hurdle, especially when they acknowledge the
|
||||
demand by providing a solution to the problem they themselves invented.
|
||||
|
||||
Crystal, despite being a compiled language, doesnt really have many good options
|
||||
for low-level things, which can be fine given it’s meant to be a replacement for
|
||||
ruby, but Crystal at least _tries_ by allowing you to link to C interfaces, but
|
||||
severely lacks many low-level features that would be nice to use if I wanted to
|
||||
use Crystal as a more general purpose language rather than a compiled Ruby
|
||||
replacement. As I’ve been getting more into low-level stuff, I’ve liked the idea
|
||||
of playing around with languages in a lower-level fashion, but Crystal is a bit
|
||||
lacking. Despite being built on top of LLVM, it cannot compile to every target
|
||||
LLVM supports, not without _patching the compiler_.
|
||||
|
||||
### Community
|
||||
|
||||
Crystal, being a smaller language, is obviously going to have less community
|
||||
support for things; even so, its support isn’t terrible. There are lots of
|
||||
“shards,” aka gems, if you’re familiar with Ruby’s terminology for these types
|
||||
of packages. However, it appears there's limited effort dedicated to
|
||||
significantly improving the developer tooling. The docs are okay, but only if
|
||||
you’re comfortable navigating and reading an API specification; outside of that,
|
||||
the docs are somewhat lacking. Shards can be helpful to give you easy solutions,
|
||||
but as with most languages that strongly lean on packages, like Rust or
|
||||
JavaScript, this ultimately leads to an over-dependence on packages and leads to
|
||||
an overrun in executable sizes.
|
||||
|
||||
## What Crystal does well
|
||||
|
||||
Not everything Crystal has to offer is negative, however. Crystal’s type safety
|
||||
and performance are nice, but its benefits are far overshadowed by its poor
|
||||
development experience and its borderline non-existence of intellisense. Its
|
||||
shard system can be nice at times. Since its syntax is basically Ruby, it’s easy
|
||||
to pick up, and can be nice to write, when it's cooperating, so basically when
|
||||
the LSP isn’t working at all. Its OOP-focused design can sometimes lead to some
|
||||
nice paradigms, even as much as I hate OOP design sometimes. It's nice to be
|
||||
able to link to C libraries if I so please; it's just unfortunate that Crystal
|
||||
attempts to create a "safe language", but isn’t nearly as deep or as mature as
|
||||
Rust, Zig, or Go. The binary size is a nice break from Golang or Rust; the
|
||||
simple blog prototype I played around with only weighed in at 4.2MB in release
|
||||
mode. It's not small by any means, but it is a stark comparison to the binary
|
||||
sizes I have dealt with when writing dramatically simpler Rust or Go programs.
|
||||
|
||||
## Conclusion
|
||||
|
||||
I really actually wanted to like Crystal, its idea is amazing, and I’ve been
|
||||
watching it since even before it went stable, but it really missed the mark for
|
||||
me, unfortunately. Its strange decisions on its syntax, and the terrible
|
||||
developer experience have really made the experience sub-par. It’s unfortunate
|
||||
that such a good idea was implemented poorly, but then again, “There are only
|
||||
two kinds of languages: the ones people complain about and the ones nobody
|
||||
uses.”
|
||||
|
||||
If you liked this blog post, maybe consider supporting me, or if you didn't,
|
||||
maybe [give Crystal a try](https://crystal-lang.org/). Regardless of what you
|
||||
think, thanks for reading, and have a nice day!
|
||||
Reference in New Issue
Block a user