DEV Community

Marvin Ahlgrimm
Marvin Ahlgrimm

Posted on

A language for humans and computers: Crystal in the AI era

At Rails World 2026, DHH described a division of labour that caught my attention: let agents write Rust and enjoy the result without having to look at the code.

At around 30:15 in the opening keynote, he puts it simply:

Agents like Rust.

I can see the appeal, and I see the same sentiment at my workplace. When an agent writes the code, the effort of writing it by hand matters less. The compiler can catch mistakes as the agent works.

But I still need and want to understand the software I am responsible for. For me, that points to Crystal.

I work with Ruby and Crystal and contribute to the Marten web framework.

Familiar syntax, static checks

Crystal combines Ruby-inspired syntax, static type checking and type inference, and compiles to native code. If you come from Ruby, much of it will look familiar.

That familiarity matters even when I am no longer typing every line. I still read the implementation, question its choices and make changes myself. Type inference keeps many annotations out of the way while the compiler checks how values are used.

Static checks on nullable values are available in other languages, too. What interests me about Crystal is having those checks in code I find straightforward to read. The agent gets feedback from the compiler; I get an implementation I can work with.

A compiler cannot choose the right tools

How do I know the agent chose the right libraries and integration approach?

Later in the talk, around 32:15, DHH describes assigning work asynchronously and reviewing the result. In my projects, that review includes the choices inside the implementation.

I recently tried setting up a small Marten application with only one prompt. During implementation, I had to correct the agent twice:

  • I wanted Marten Turbo and Marten Stimulus. The agent used npm to install the JavaScript packages instead of the CLI provided by Marten-importmap, which was the integration approach I wanted.
  • It started developing its own throttling middleware instead of using Marten-throttle.

I maintain these Marten shards, so I recognised the detours. npm was not the setup I wanted, and the custom middleware would have given me more code to maintain. Neither choice was a type error. Correcting them took knowledge of the project and its libraries.

Where compiler feedback has helped me

A few months ago, when the models I used were less capable, I repeatedly saw compilation errors in generated Crystal code. Some involved incorrect handling of String? values. In some cases, compiler feedback led the agent towards a correction without me having to explain the type problem myself.

A small example shows what the compiler can catch:

name = ARGV[0]?
puts name.upcase
Enter fullscreen mode Exit fullscreen mode

The first argument might be missing. Its type is String | Nil, also written String?. Calling upcase directly is rejected because Nil does not provide that method.

An ordinary conditional handles the missing value:

name = ARGV[0]?

if name
  puts name.upcase
else
  puts "GUEST"
end
Enter fullscreen mode Exit fullscreen mode

Inside the if branch, the compiler knows the local variable cannot be nil. Crystal documents this type narrowing and its limitations, including why repeated getter calls cannot always be narrowed in the same way.

Writing another if/else can feel tedious. I appreciate it more when an agent writes the code: I can see what it decided to do when the argument is missing.

Compiling still leaves a decision open

There is another way to get past the error:

name = ARGV[0]?
puts name.not_nil!.upcase
Enter fullscreen mode Exit fullscreen mode

This compiles, but raises an exception when the argument is missing. The standard-library documentation describes that behaviour and recommends avoiding not_nil! where possible.

Ameba's Lint/NotNil rule flags the assertion in this example. With the rule enabled, the linter gives the agent feedback about a shortcut the compiler accepts.

The requirement is still open. Should a missing name produce a guest label? Should it stop the operation? Is absence supposed to be impossible? The compiler accepts both the guest fallback and the assertion. I still have to decide which matches the requirement.

I still need end-to-end checks in both Marten applications and fixes or features for Meridian, my deployment tool. Parts of that work can already be automated, but the agents I use do not reliably cover all my requirements.

Code and tests generated from the same misunderstanding can agree with each other.

Libraries and compiler feedback

The Marten experiment showed that I cannot assume the agent will find the libraries I want. In a smaller ecosystem, I need to name them and provide current documentation.

A study on code generation for low-resource programming languages explores how tools and feedback can help models work with less familiar languages. I do not know what caused the mistakes in my experiments. I check the agent's library choices and language usage either way.

Waiting for the compiler also adds up. Long development builds slow down every round of corrections.

The Generative Compilation preprint explores compiler feedback during the generation of partial Rust programs and reports improvements over feedback after complete generation. Its results suggest that the timing of compiler feedback matters as well as the checks themselves.

What Rust offers

Rust's ownership model and concurrency checks provide guarantees Crystal's garbage collector does not replace. Rust also offers cargo check and Clippy as feedback tools.

I have not benchmarked my Crystal workflow against Rust. If you work with Rust every day, you may find its code easier to review.

Crystal can be hard to read, too. Complex macros can hide what the code does, and its unsafe operations need careful review. I have to account for that effort even when an agent handles the implementation.

The code I still want to understand

I expect agents to do more implementation work. In my projects, I still need and want to judge the result, check requirements and understand enough to make changes.

With Crystal, static feedback helps the agent correct certain mistakes, and its syntax helps me inspect the decisions that remain.

Crystal calls itself a language for humans and computers. As computers take on more of the writing, the human side becomes a reason to choose it. I want to delegate implementation and remain capable of understanding and changing the result. Crystal makes that practical for me.

Top comments (0)