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.
📋 Daftar Isi
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
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
# 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
# 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
# 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.
# 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.
# 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.
# 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.
# 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
9. Web Development dengan Kemal
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