Crystal compiles to native code (using LLVM) but looks exactly like Ruby. v1.12 continues improving Windows support and parallelism.
When to Use
- High-Performance Ruby-Like Applications: Building compiled native binaries with Ruby-like elegance and C-like execution speed.
- Fast Microservices & APIs (Kemal / Lucky): Serving high-throughput REST and WebSocket APIs with sub-millisecond response latencies.
- CLI Tool Development: Compiling single, self-contained standalone executable binaries without external runtime dependencies.
- Concurrent Network Processing: Handling thousands of concurrent connections using lightweight CSP fibers and channels.
Quick Start
# Define typed struct with automatic getters
record User, id : Int32, name : String, email : String
users = [ User.new(1, "Alice", "alice@example.com"), User.new(2, "Bob", "bob@example.com") ]
users.each do |user| puts "#{user.name} (#{user.email})" end
## Core Concepts
### Compile-Time Static Type Inference
Provides the syntax of Ruby with the type-safety of a compiled language without redundant type annotations:
```crystal
# Crystal infers types at compile time
def compute_discount(price, coupon = nil)
if coupon == "VIP"
price * 0.8
else
price
end
end
puts compute_discount(100) # 100
puts compute_discount(100, "VIP") # 80.0
Concurrency via Fibers & Channels (CSP)
Lightweight cooperative green threads scheduled cooperatively on an event loop:
channel = Channel(String).new
# Spawn lightweight fiber
spawn do
sleep 0.5.seconds
channel.send("Processing complete")
end
puts "Waiting for background fiber..."
message = channel.receive
puts "Received: #{message}"
C-Binding Interoperability
Calls external C dynamic libraries natively with zero wrapper boilerplate:
@[Link("m")]
lib LibM
fun sqrt(x : Float64) : Float64
end
puts LibM.sqrt(144.0) # 12.0
Common Patterns
Fast C Library Interoperability
Problem: Calling native C libraries without writing complex wrapper boilerplate.
Solution:
Declare lib binding directly in Crystal:
lib LibC
fun puts(str : UInt8*) : Int32
fun getpid : Int32
end
pid = LibC.getpid
puts "Current process ID: #{pid}"
Best Practices
Do:
- Build with
--releasefor Production: Always pass--releaseto enable aggressive LLVM dead-code elimination and inlining. - Use Non-Nilable Types: Benefit from Crystal's compile-time null safety; handle
nilexplicitly with.tryorif val. - Use Static Builds via Docker/Alpine: Compile fully static Linux binaries using
docker run --rm -v $(pwd):/workspace crystallang/crystal:latest-alpine. - Structure Concurrency Around Channels: Share memory by communicating through typed channels rather than shared mutable pointers.
Don't:
- Use
Object#asblindly: Unchecked type assertions cause runtime type cast exceptions; use pattern matching or.is_a?. - Perform blocking CPU-bound loops in fibers: Cooperatively yield CPU control using
Fiber.yieldin tight loops. - Deploy debug builds: Non-release Crystal binaries are significantly larger and order of magnitude slower.
Troubleshooting
| Error | Cause | Solution |
|---|---|---|
type must be (Type), not (Type | Nil) |
Nil-checking missing before accessing methods on nullable variable. | Use var.not_nil! or guard with if var. |
Can't use Type as type in union |
Incompatible types combined without common ancestor. | Explicitly annotate union type or cast with .as(TargetType). |
undefined constant in shard |
Missing dependency or shards.yml not resolved. |
Run shards install to install project dependencies. |