DEV Community

kojix2
kojix2

Posted on • Edited on

12 Things I Learned Writing CLI Tools in Crystal

I love the Crystal programming language. For the past two or three years, I have been building command-line tools with it. During this time, I often compared it with Ruby, and I encountered many differences, discoveries, and obstacles. In this article, I will share them.

1. Similarity to Ruby

Crystal looks very similar to Ruby. Many common Ruby idioms also work in Crystal. Crystal is statically typed, but most of the time you do not need to write types explicitly. Type inference will do the work for you.

2. Use DeepWiki

DeepWiki is very useful for learning Crystal. For a niche language, it is one of the best resources. You can even ask questions in your native language.

3. Arrays and Hashes cannot mix types

In Crystal, you cannot freely mix different types in an Array or Hash. Ruby allows this, but Crystal does not. You can use union types, but usually it is better to avoid them. Instead, consider one of these options:

At first this may feel inconvenient, but you get used to it.

# Array(Int32 | String | Symbol) - not recommended arr = [1, "two", :three] # OK: Tuple for fixed positions t = {1, "two", :three} # OK: record for structured data record Item, id : Int32, name : String, tag : Symbol items = [ Item.new(1, "apple", :fruit), Item.new(2, "orange", :fruit), ] 
Enter fullscreen mode Exit fullscreen mode

4. No eval

Crystal does not have eval. This is one big difference from Ruby.
If you really need dynamic evaluation, you should use Ruby. Another choice is to embed mruby or use a library like Anyolite. Crystal itself has an interpreter, but it is not practical and slower than Ruby or mruby.

# Ruby code = "1 + 2" puts eval(code) # => 3 
Enter fullscreen mode Exit fullscreen mode
# Crystal has no eval # You must design differently 
Enter fullscreen mode Exit fullscreen mode

5. Method overloading

In Ruby, it is common to branch on the argument type inside one method.
In Crystal, it is more natural to use method overloading. This makes the code clearer.

def square(x : Int32) : Int32 x * x end def square(x : String) : Int32 square(x.to_i) end puts square(12) # => 144 puts square("12") # => 144 
Enter fullscreen mode Exit fullscreen mode

6. Return types should be consistent

In Ruby, a method can return values of different types. In Crystal, if the return type is not clear, you will run into trouble. If you want to return different types, you should split the method. You can use a union type, but it is not recommended.

# not recommended def maybe_value(flag : Bool) : Int32 | String flag ? 42 : "forty-two" end 
Enter fullscreen mode Exit fullscreen mode
def value_int : Int32 42 end def value_str : String "forty-two" end 
Enter fullscreen mode Exit fullscreen mode

7. Handling Nil

Pay attention to whether a variable can be Nil.
If it can, you need to handle it with not_nil!, if val = maybe_val, or the safe navigation operator.

name : String? = nil if n = name puts n.upcase else raise "name is nil" end 
Enter fullscreen mode Exit fullscreen mode

8. Garbage collection

Crystal uses LLVM and relies on an external GC (libgc).
Performance is often close to Rust or Nim, but memory profiling and tuning can be difficult. Also, the timing of GC is not predictable, so Crystal may not be suitable for real-time systems.

9. Asynchronous I/O

Asynchronous I/O is available by default. Some developers feel it is easier to use than in Rust.

10. Linking when distributing

Crystal programs are usually linked with libgc and other libraries such as libpcre2. Be careful when distributing binaries.

  • Linux: You can build statically linked binaries with GitHub Actions + Docker + musl
  • macOS: You can prepare a Homebrew Tap, or build portable binaries with static linking for libgc, libpcre2, and others

See also: github actions workflow in lolcat.cr

11. Windows support

Crystal now works on Windows (MSVC / MinGW64) more stably than before. Parallel execution also works. However, solving C library dependencies can still be painful. If you are not familiar with Windows, you may need to ask AI for help.

12. Limitations of OptionParser

The standard OptionParser does not support combined short options.
So ls -l -h works, but ls -lh does not.
I plan to create a pull request to fix this in the future.

Conclusion

Writing command-line tools in Crystal is sometimes painful. But at the same time, you learn a lot. I believe the “best days” of the Crystal language are not in the past or present, but in the future.


This post was originally based on my reply to a thread on Reddit, then expanded into a Japanese article on Qiita, and now translated into English with the help of ChatGPT.

Top comments (0)