Crystal: Sintaks Ruby dengan Performa C

Crystal adalah bahasa pemrograman yang menawarkan sintaks seindah Ruby dengan performa secepat C. Melalui type inference, static typing, dan LLVM backend, Crystal menghasilkan binary native yang sangat cepat.

1. Mengapa Crystal?

Crystal lahir dari kecintaan terhadap sintaks Ruby dan kebutuhan akan performa yang lebih baik. Bahasa ini memiliki sintaks yang sangat mirip Ruby — bahkan Ruby developer bisa langsung produktif — tetapi menghasilkan binary native yang 10-50x lebih cepat.

  • Sintaks Ruby — Familiar untuk Ruby developer
  • Static Typing dengan Type Inference — Aman tanpa boilerplate
  • Compiles to Native — Menggunakan LLVM, menghasilkan binary cepat
  • Built-in Concurrency — Fibers dan channels seperti Go
  • C Bindings — Panggil C library tanpa overhead
  • Macros — Metaprogramming yang powerful
💡 Crystal vs Ruby

Jika Anda sudah tahu Ruby, Anda sudah tahu ~80% Crystal. Bedanya: Crystal adalah statically typed, compiled ke native binary, dan jauh lebih cepat. Crystal ideal untuk CLI tools, web servers, dan microservices.

2. Instalasi dan Setup

Bash
# macOS
brew install crystal

# Ubuntu/Debian
curl -fsSL https://crystal-lang.org/install.sh | sudo bash

# Windows (WSL recommended)
# Atau download dari https://crystal-lang.org/install/

# Arch Linux
sudo pacman -S crystal shards

# Verifikasi
crystal --version
# Crystal 1.11.x

# Buat proyek baru
crystal init app my_project
cd my_project

# Struktur:
# my_project/
# ├── shard.yml
# ├── spec/
# │   └── my_project_spec.cr
# ├── src/
# │   └── my_project.cr
# └── README.md

# Compile dan jalankan
crystal run src/my_project.cr

# Build binary
crystal build src/my_project.cr --release

# Jalankan test
crystal spec

3. Dasar-Dasar Crystal

Variables dan Types

src/basics.cr
# Variables
nama = "BeebaneLabs"   # String
umur = 25              # Int32
tinggi = 1.75          # Float64
aktif = true           # Bool

# Constants
PI = 3.14159
MAX_SIZE = 100

# Type annotation (opsional, Crystal infers types)
x : Int32 = 42
y : Float64 = 3.14
s : String = "hello"

# Nilable types
mungkin_nil : String? = nil
mungkin_nil = "sekarang ada"

# Symbols
status = :active
role = :admin

# Nil
typeof(nil)  # Nil

# String interpolation
puts "Nama: #{nama}, Umur: #{umur}"

# String methods
puts nama.upcase
puts nama.downcase
puts nama.includes?("Bee")
puts nama.gsub("Bee", "B")
puts nama.reverse

# Ranges
(1..10).each { |i| print "#{i} " }
(1...10).each { |i| print "#{i} " }  # exclusive

# Array
angka = [1, 2, 3, 4, 5]
buah = ["apel", "jeruk", "mangga"]
mixed = [1, "dua", 3.0]  # Array(Int32 | String | Float64)

# Array operations
angka << 6              # push
angka.push(7)
angka.pop
angka.includes?(3)      # true
angka.select { |x| x > 3 }  # [4, 5]
angka.map { |x| x * 2 }     # [2, 4, 6, 8, 10]
angka.reduce(0) { |acc, x| acc + x }  # 15
angka.sort
angka.reverse

# Hash
user = {"nama" => "Andi", "umur" => 25}
user = {"nama": "Andi", "umur": 25}  # Symbol keys
user["nama"]  # "Andi"
user.has_key?("email")

# Tuple
t = {1, "hello", 3.14}
t[0]  # 1

Control Flow dan Functions

src/control.cr
# If / elsif / else
nilai = 85
if nilai >= 90
  grade = "A"
elsif nilai >= 80
  grade = "B"
elsif nilai >= 70
  grade = "C"
else
  grade = "D"
end

# Unless (negasi if)
unless umur < 17
  puts "Boleh masuk"
end

# Ternary
status = nilai >= 70 ? "Lulus" : "Tidak Lulus"

# Case / when
case nilai
when 90..100
  puts "A"
when 80..89
  puts "B"
when 70..79
  puts "C"
else
  puts "D"
end

# For loops
3.times { |i| puts "Iterasi #{i}" }
(1..10).each { |i| puts i }
angka.each { |n| puts n }
angka.each_with_index { |n, i| puts "#{i}: #{n}" }

# While
i = 0
while i < 10
  puts i
  i += 1
end

# Functions
def tambah(a, b)
  a + b
end

# Dengan type restrictions
def kali(a : Int32, b : Int32) : Int32
  a * b
end

# Default parameters
def greet(nama : String, greeting : String = "Halo")
  "#{greeting}, #{nama}!"
end

# Named arguments
def buat_user(*, nama : String, umur : Int32)
  "User: #{nama}, Umur: #{umur}"
end
buat_user(nama: "Andi", umur: 25)

# Splat arguments
def jumlah(*angka : Int32)
  angka.sum
end
jumlah(1, 2, 3, 4, 5)  # 15

# Block
def with_logging(name, &block)
  puts "Mulai: #{name}"
  result = block.call
  puts "Selesai: #{name}"
  result
end

with_logging("hitung") { 1 + 2 }

# Proc
kuadrat = ->(x : Int32) { x * x }
kuadrat.call(5)  # 25

4. Type Inference dan Static Typing

Crystal menggunakan type inference yang sangat canggih. Anda jarang perlu menulis type annotations, tetapi compiler tahu persis tipe setiap variabel dan ekspresi.

src/types.cr
# Type inference otomatis
x = 42           # Compiler tahu: Int32
y = 3.14         # Compiler tahu: Float64
s = "hello"      # Compiler tahu: String
a = [1, 2, 3]   # Compiler tahu: Array(Int32)

# Union types
val = rand > 0.5 ? 42 : "hello"  # Int32 | String

# Nilable types
def find_user(id : Int32)
  if id == 1
    "Andi"
  else
    nil
  end
end
# Return type: String?

user = find_user(1)
if user
  puts user.upcase  # Crystal tahu user bukan nil di sini
end

# Type casting
if user.is_a?(String)
  puts user.upcase  # Crystal tahu ini String
end

# Case dengan type
case val
when Int32
  puts "Integer: #{val * 2}"
when String
  puts "String: #{val.upcase}"
end

# Struct (value type, lebih cepat dari class)
struct Point
  getter x : Float64
  getter y : Float64

  def initialize(@x, @y)
  end

  def distance_to(other : Point)
    Math.sqrt((x - other.x)**2 + (y - other.y)**2)
  end
end

# Class (reference type)
class User
  property name : String
  property age : Int32
  property email : String?

  def initialize(@name, @age, @email = nil)
  end

  def adult?
    @age >= 17
  end

  def to_s(io)
    io << "User(#{@name}, #{@age})"
  end
end

# Generics
class Container(T)
  @items = [] of T

  def push(item : T)
    @items << item
  end

  def pop : T?
    @items.pop?
  end

  def size
    @items.size
  end
end

int_box = Container(Int32).new
int_box.push(42)
str_box = Container(String).new
str_box.push("hello")

# Modules (mixins)
module Printable
  abstract def to_s(io : IO)

  def print
    puts to_s
  end
end

module Serializable
  def to_json
    # serialize to JSON
  end
end

class Product
  include Printable
  include Serializable

  getter name : String
  getter price : Float64

  def initialize(@name, @price)
  end

  def to_s(io)
    io << "#{@name}: Rp #{@price}"
  end
end

5. Macros

Macros di Crystal beroperasi pada AST (Abstract Syntax Tree) dan memungkinkan code generation saat compile time.

src/macros.cr
# Basic macro
macro greet(name)
  puts "Hello, {{name.id}}!"
end

greet("World")  # Hello, World!

# Property macros (seperti attr_accessor di Ruby)
macro property(*names)
  {% for name in names %}
    getter {{name.id}}
    setter {{name.id}}
  {% end %}
end

class Config
  property host, port, debug
  @host : String = "localhost"
  @port : Int32 = 8080
  @debug : Bool = false
end

# Validation macro
macro validate_presence(*fields)
  {% for field in fields %}
    def valid_{{field.id}}?
      !@{{field.id}}.nil? && @{{field.id}}.to_s.size > 0
    end
  {% end %}
end

class Form
  def initialize(@name : String?, @email : String?, @age : Int32?)
  end
  validate_presence name, email
end

# Logging macro
macro log_method(method_name)
  def {{method_name.id}}
    puts "[LOG] Memanggil {{method_name.id}}"
    result = previous_def
    puts "[LOG] {{method_name.id}} selesai dengan: #{result}"
    result
  end
end

# Conditional compilation
{% if flag?(:linux) %}
  puts "Running on Linux"
{% elsif flag?(:darwin) %}
  puts "Running on macOS"
{% elsif flag?(:win32) %}
  puts "Running on Windows"
{% end %}

# Macro untuk membuat enum-like behavior
macro define_status(*values)
  enum Status
    {% for value in values %}
      {{value.id}}
    {% end %}
  end
end

define_status Active, Inactive, Pending, Banned

# Annotation macros
@[JSON::Field(key: "user_name")]
getter name : String

@[Link("m")]  # Link dengan C library
lib LibMath
  fun sqrt(x : Float64) : Float64
end

6. Concurrency (Fibers dan Channels)

Crystal menggunakan Fibers (lightweight threads) dan Channels untuk concurrency, mirip dengan model Go.

src/concurrency.cr
# Fibers - lightweight concurrent execution
spawn do
  5.times do |i|
    puts "Fiber 1: #{i}"
    sleep 0.1
  end
end

spawn do
  5.times do |i|
    puts "Fiber 2: #{i}"
    sleep 0.1
  end
end

sleep 1  # Tunggu fibers selesai

# Channels - komunikasi antar fibers
channel = Channel(String).new

spawn do
  channel.send("Hello from fiber!")
end

message = channel.receive
puts message  # "Hello from fiber!"

# Producer-consumer pattern
def producer(ch : Channel(Int32))
  10.times do |i|
    ch.send(i * i)
    sleep 0.05
  end
  ch.close
end

def consumer(ch : Channel(Int32))
  while value = ch.receive?
    puts "Received: #{value}"
  end
end

ch = Channel(Int32).new
spawn producer(ch)
consumer(ch)

# Select - wait untuk multiple channels
ch1 = Channel(String).new
ch2 = Channel(String).new

spawn { sleep 0.1; ch1.send("dari channel 1") }
spawn { sleep 0.2; ch2.send("dari channel 2") }

2.times do
  select
  when msg = ch1.receive
    puts "ch1: #{msg}"
  when msg = ch2.receive
    puts "ch2: #{msg}"
  end
end

# Mutex untuk shared state
mutex = Mutex.new
counter = 0

10.times do
  spawn do
    100.times do
      mutex.synchronize do
        counter += 1
      end
    end
  end
end

sleep 1
puts "Counter: #{counter}"  # 1000

7. C Bindings

Crystal bisa memanggil C library secara langsung menggunakan lib bindings.

src/c_bindings.cr
# Definisikan C library binding
@[Link("c")]
lib LibC
  fun printf(format : UInt8*, ...) : Int32
  fun system(command : UInt8*) : Int32
  fun time(t : Int64*) : Int64
end

# Definisikan custom C library binding
@[Link("m")]
lib LibMath
  fun sqrt(x : Float64) : Float64
  fun pow(base : Float64, exp : Float64) : Float64
  fun sin(x : Float64) : Float64
  fun cos(x : Float64) : Float64
  fun log(x : Float64) : Float64
  fun floor(x : Float64) : Float64
  fun ceil(x : Float64) : Float64
end

# Using C bindings
puts LibMath.sqrt(144.0)     # 12.0
puts LibMath.pow(2.0, 10.0) # 1024.0
puts LibMath.sin(Math::PI / 2)  # 1.0

# Struct C binding
@[Link("sqlite3")]
lib LibSQLite
  struct SQLite3
    # opaque
  end

  fun open(path : UInt8*, db : SQLite3**) : Int32
  fun close(db : SQLite3*) : Int32
  fun exec(db : SQLite3*, sql : UInt8*, callback : Void*, arg : Void*, errmsg : UInt8**) : Int32
end

# Custom C code compilation
# Buat file mylib.c:
# int multiply(int a, int b) { return a * b; }

# Compile ke .o:
# gcc -c mylib.c -o mylib.o

# Di Crystal:
@[Link(ldflags: "#{__DIR__}/mylib.o")]
lib LibMyLib
  fun multiply(a : Int32, b : Int32) : Int32
end

puts LibMyLib.multiply(6, 7)  # 42

# FFI dengan OpenSSL
@[Link("ssl")]
lib LibSSL
  # ... SSL bindings
end

# FFI dengan zlib
@[Link("z")]
lib LibZ
  fun compress(dest : UInt8*, dest_len : UInt64*, source : UInt8*, source_len : UInt64) : Int32
end

8. Shards (Package Manager)

shard.yml
# shard.yml - file konfigurasi proyek Crystal
name: my_project
version: 0.1.0

authors:
  - Andi 

dependencies:
  kemal:
    github: kemalframework/kemal
  crecto:
    github: Crecto/crecto
  jwt:
    github: crystal-community/jwt

development_dependencies:
  spec:
    github: crystal-lang/crystal-spec

crystal: ">= 1.10.0"
license: MIT
Bash
# Install dependencies
shards install

# Update dependencies
shards update

# Build proyek
shards build

# Jalankan binary
./bin/my_project

# Publish shard
shards publish

# Popular shards:
# - kemal (web framework)
# - amber (full-stack web framework)
# - crecto (ORM, seperti Ecto)
# - granite (ORM)
# - spec (testing)
# - kemalyst (WebSocket framework)
# - db (database abstraction)

9. Web Development dengan Kemal

src/server.cr
require "kemal"
require "json"

# Simple GET route
get "/" do
  "Hello from Crystal!"
end

# JSON API
get "/api/users" do
  users = [
    {"id" => 1, "name" => "Andi", "age" => 25},
    {"id" => 2, "name" => "Budi", "age" => 30},
    {"id" => 3, "name" => "Citra", "age" => 22},
  ]
  users.to_json
end

# Route parameters
get "/api/users/:id" do |env|
  id = env.params.url["id"]
  {"id" => id, "name" => "User #{id}"}.to_json
end

# POST with JSON body
post "/api/users" do |env|
  name = env.params.json["name"].as(String)
  age = env.params.json["age"].as(Int64)
  status "User #{name} created (age: #{age})"
  {"status" => "created", "name" => name, "age" => age}.to_json
end

# Middleware
before_all do |env|
  env.response.headers["X-Powered-By"] = "Crystal"
end

# Error handling
error 404 do
  "Halaman tidak ditemukan"
end

# Static files
serve_static({"gzip" => true})

# WebSocket
ws "/chat" do |socket|
  socket.on_message do |message|
    socket.send "Echo: #{message}"
  end
end

Kemal.config.port = 3000
Kemal.run

🧠 Kuis: Crystal Language

1. Apa keunggulan utama Crystal dibanding Ruby?

  • Sintaks yang lebih indah
  • Static typing dan compilasi ke native binary
  • Lebih banyak library
  • Garbage collector yang lebih baik

2. Apa itu Fiber di Crystal?

  • Lightweight coroutine yang dijadwalkan oleh runtime
  • Thread OS yang berat
  • Library untuk networking
  • Type inference engine

3. Bagaimana cara memanggil C library dari Crystal?

  • Menggunakan FFI gem
  • Tidak bisa, Crystal tidak kompatibel dengan C
  • Menggunakan @[Link] annotation dan lib declaration
  • Menggunakan extern keyword

4. Apa itu Shards di Crystal?

  • Fragments dari error messages
  • Package manager untuk Crystal
  • Macro system Crystal
  • Testing framework

5. Apa perbedaan struct dan class di Crystal?

  • Struct adalah value type, class adalah reference type
  • Tidak ada perbedaan
  • Struct hanya untuk angka, class untuk semua
  • Class tidak mendukung inheritance

📚 Sumber Belajar Lanjutan