| layout | default |
|---|---|
| title | Tutorial |
| parent | Guides |
| nav_order | 1 |
This tutorial walks you through installing Konpeito and running your first compiled Ruby code.
| Dependency | Version | Required for |
|---|---|---|
| Ruby | 4.0.1+ | Always (runs the compiler) |
| LLVM | 20 | CRuby native backend, mruby backend |
| Java | 21+ | JVM backend |
| mruby | 3.x or 4.x | mruby backend |
LLVM, Java, and mruby are needed depending on which backend you use. You only need one.
gem install konpeitomacOS:
brew install llvm@20
ln -sf /opt/homebrew/opt/llvm@20/lib/libLLVM-20.dylib /opt/homebrew/lib/
gem install ruby-llvmUbuntu / Debian:
sudo apt install llvm-20 clang-20
gem install ruby-llvmFedora:
sudo dnf install llvm20 clang20
gem install ruby-llvmmacOS:
brew install openjdk@21Ubuntu / Debian:
sudo apt install openjdk-21-jdkFedora:
sudo dnf install java-21-openjdk-develkonpeito doctor # check CRuby backend
konpeito doctor --target jvm # check JVM backendYou should see green checkmarks for each dependency. Two warnings are expected and can be safely ignored:
- ASM tool: WARNING — The ASM tool (bytecode assembler for the JVM backend) is built automatically the first time you run a JVM build. No action needed.
- Config: WARNING — No
konpeito.tomlfound in the current directory. This is normal when you haven't created a project yet. You can create one withkonpeito init, or simply pass source files directly tokonpeito build.
# hello.rb
module Hello
def self.greet(name)
"Hello, #{name}!"
end
endkonpeito build hello.rb # → hello.bundle (macOS) / hello.so (Linux)Use it from Ruby:
require_relative "hello"
puts Hello.greet("World") # => "Hello, World!"The compiled extension is a standard CRuby C extension. It loads into any Ruby process just like a gem written in C. Wrapping your code in a module avoids polluting the top-level namespace — this is the recommended pattern for extension libraries.
# hello_jvm.rb
puts "Hello from Konpeito!"konpeito build --target jvm --run hello_jvm.rbOn the very first JVM build, you will see a "Building ASM tool" message. This is a one-time setup.
Expected output:
Compiling hello_jvm.rb (jvm)
Building ASM tool (first-time setup)...
ASM tool ready.
Running: java -jar hello_jvm.jar
Hello from Konpeito!
Finished in 0.9s -> hello_jvm.jar (36 KB)
The generated JAR is standalone — no Ruby installation needed on the target machine.
When Konpeito compiles your code, each operation falls into one of two categories:
- Native — The compiler resolved the types and emitted native CPU instructions (e.g. LLVM:
add i64,fadd double,getelementptr) or typed JVM bytecode (e.g.iadd,dadd). No Ruby method dispatch overhead. - Dynamic fallback — The compiler couldn't determine the type. The call compiles to
rb_funcallv(LLVM) orinvokedynamic(JVM), which runs at the same speed as regular Ruby. The compiler emits a warning so you know where the boundaries are.
Adding RBS type annotations promotes dynamic fallbacks to native dispatch. You can leave them dynamic if you don't need the speed there — it's your call.
When your code says require "some_gem", the compiler checks whether the gem's source is available on the compile-time load path (specified by -I):
- On the load path (
-I) — The gem's source files are compiled together with your code into a single extension. Method calls between your code and the gem use direct dispatch, monomorphization, and inlining. - Not on the load path — The compiler emits a
rb_require("some_gem")call so CRuby loads the gem at runtime. Your compiled code can still call the gem's methods, but those calls go throughrb_funcallv(dynamic dispatch). This still works correctly — it's just not optimized.
In practice, many applications only need to compile their own code. Gems like UI frameworks or database drivers are bottlenecked by rendering or external services, so compiling them provides little benefit. Use -I when you want to maximize optimization across the entire codebase.
Compile performance-critical functions into a native extension and require it from your regular Ruby application.
# physics.rb
module Physics
def self.distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
dx * dx + dy * dy
end
def self.sum_distances(xs, ys, n)
total = 0.0
i = 0
while i < n - 1
total = total + distance(xs[i], ys[i], xs[i + 1], ys[i + 1])
i = i + 1
end
total
end
endWithout RBS, HM inference will still resolve types from literals and call sites. But explicit types let the compiler optimize more aggressively.
Option A: Inline RBS (recommended for getting started)
Write type annotations directly in the Ruby source using rbs-inline comments:
# physics.rb
# rbs_inline: enabled
module Physics
#: (Float x1, Float y1, Float x2, Float y2) -> Float
def self.distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
dx * dx + dy * dy
end
#: (Array[Float] xs, Array[Float] ys, Integer n) -> Float
def self.sum_distances(xs, ys, n)
total = 0.0
i = 0
while i < n - 1
total = total + distance(xs[i], ys[i], xs[i + 1], ys[i + 1])
i = i + 1
end
total
end
endOption B: Separate RBS file
# physics.rbs
module Physics
def self.distance: (Float x1, Float y1, Float x2, Float y2) -> Float
def self.sum_distances: (Array[Float] xs, Array[Float] ys, Integer n) -> Float
end# Inline RBS (Option A)
konpeito build --inline physics.rb
# Separate RBS file (Option B)
konpeito build physics.rbUse -v to see what the compiler inferred and where dynamic fallbacks occur:
konpeito build -v physics.rb# app.rb — regular Ruby, NOT compiled by Konpeito
require_relative "physics"
xs = Array.new(10000) { rand }
ys = Array.new(10000) { rand }
puts Physics.sum_distances(xs, ys, 10000)ruby app.rbWhat's native: distance is fully native — dx * dx + dy * dy compiles to fmul double + fadd double instructions. The while loop in sum_distances is a native counter loop.
What's dynamic: xs[i] calls rb_funcallv on the Ruby Array (because the Array itself is a CRuby object, not a NativeArray). If you need that inner access to be native too, use NativeArray[Float]:
NativeArray[Float] stores unboxed double values in contiguous memory. Array element access becomes a direct getelementptr + load — no method dispatch at all.
Since NativeArray is a Konpeito-specific type, it must be created and accessed within the same compiled scope. Here we put the NativeArray creation, population, and computation together in one method:
# physics_native.rb
# rbs_inline: enabled
module Physics
#: (Float, Float, Float, Float) -> Float
def self.distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
dx * dx + dy * dy
end
def self.run
n = 10000
xs = NativeArray.new(n)
ys = NativeArray.new(n)
i = 0
while i < n
xs[i] = i * 0.0001
ys[i] = i * 0.0002
i = i + 1
end
total = 0.0
i = 0
while i < n - 1
total = total + distance(xs[i], ys[i], xs[i + 1], ys[i + 1])
i = i + 1
end
puts total
end
end
Physics.runkonpeito run physics_native.rbTip:
konpeito runcaches compiled artifacts in.konpeito_cache/run/. On subsequent runs, if no source or RBS files have changed, compilation is skipped entirely. Use--no-cacheto force recompilation, or--clean-run-cacheto wipe the cache.
Note: When both
physics_native.rbandphysics_native.bundleexist in the same directory,require "./physics_native"loads the.rbsource file first.konpeito runavoids this problem by handling the load path automatically. If you need to build and run separately, output to a different directory:konpeito build -o build/physics_native.bundle physics_native.rb ruby -r ./build/physics_native -e ""
Important: Local NativeArray values are stack-allocated pointers and cannot be passed as arguments to other methods via CRuby's method dispatch. Always create and use local NativeArrays within the same function scope. If you need arrays shared across functions, use module NativeArray instead.
Now xs[i] and ys[i] are also native — the entire loop runs without touching Ruby's method dispatch.
For game state, simulation data, or any arrays that need to be accessed from multiple functions, you can declare fixed-size NativeArrays as module instance variables in RBS. The compiler generates LLVM global arrays — no C wrapper file needed.
Using inline RBS (rbs_inline: enabled), the module declaration and array types live in the same file:
# physics.rb
# rbs_inline: enabled
# @rbs module PhysicsData
# @rbs @xs: NativeArray[Float, 10000]
# @rbs @ys: NativeArray[Float, 10000]
# @rbs end
#: (Float, Float, Float, Float) -> Float
def distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
dx * dx + dy * dy
end
def init_data
i = 0
while i < 10000
PhysicsData.xs[i] = i * 0.0001
PhysicsData.ys[i] = i * 0.0002
i = i + 1
end
end
#: () -> Float
def compute_total
total = 0.0
i = 0
while i < 9999
total = total + distance(PhysicsData.xs[i], PhysicsData.ys[i],
PhysicsData.xs[i + 1], PhysicsData.ys[i + 1])
i = i + 1
end
total
end
def run_physics
init_data
compute_total
endkonpeito run --inline physics.rbOr with a separate RBS file:
# physics.rbs
module PhysicsData
@xs: NativeArray[Float, 10000]
@ys: NativeArray[Float, 10000]
endThe syntax is NativeArray[T, N] where T is the element type (Integer or Float) and N is the fixed size. Access uses ModuleName.field_name[index] and ModuleName.field_name[index] = value.
Note: Module NativeArray is available on the LLVM (CRuby) and mruby backends. JVM backend is not yet supported. See
examples/mruby_space_invaders/for a full game using this pattern.
Compile an entire Ruby application. The compiler traces require / require_relative statements and compiles everything it can find on the load path into a single extension.
kumiki is a cross-platform desktop UI framework for Ruby.
gem install kumiki# counter.rb
require "kumiki"
include Kumiki
class CounterComponent < Component
def initialize
super
@count = state(0)
end
def view
column(padding: 16.0, spacing: 8.0) {
text "Count: #{@count}", font_size: 32.0, color: 0xFFC0CAF5, align: :center
row(spacing: 8.0) {
button(" - ") { @count -= 1 }
button(" + ") { @count += 1 }
}
}
end
end
frame = RanmaFrame.new("Kumiki Counter", 400, 300)
app = App.new(frame, CounterComponent.new)
app.runOption A: Compile everything (whole application)
konpeito build -I /path/to/kumiki/lib counter.rb-I adds kumiki's lib/ directory to the load path. The compiler resolves require "kumiki" and all of kumiki's internal requires, compiling 59 files into a single counter.bundle (971 KB). Method calls between your code and kumiki use direct dispatch; monomorphization and inlining are applied across the entire codebase.
Option B: Compile only your code (extension library)
konpeito build counter.rbWithout -I, the compiler compiles only counter.rb (~50 KB). kumiki is loaded at runtime by CRuby via rb_require. Your code is still compiled natively, but calls into kumiki go through rb_funcallv (dynamic dispatch).
Run:
konpeito run counter.rbOr build to a separate directory to avoid the .rb / .bundle name conflict:
konpeito build -o build/counter.bundle counter.rb
ruby -r ./build/counter -e ""-r ./build/counterloads the extension. ItsInitfunction runs the top-level code (creates the frame, component, and enters the event loop).-e ""provides an empty script so Ruby doesn't wait for stdin.- Do not use
ruby -r ./counter -e ""in the same directory ascounter.rb— Ruby loads.rbbefore.bundle, so it would run the uncompiled source instead.
# physics_jvm.rb
def distance(x1, y1, x2, y2)
dx = x2 - x1
dy = y2 - y1
dx * dx + dy * dy
end
def sum_distances(n)
total = 0.0
i = 0
while i < n
total = total + distance(i * 1.0, 0.0, 0.0, i * 2.0)
i = i + 1
end
total
end
puts sum_distances(1000)konpeito build --target jvm --run physics_jvm.rbOr produce a JAR and run it separately:
konpeito build --target jvm -o physics.jar physics_jvm.rb
java -jar physics.jarThe JAR is self-contained — no Ruby installation needed on the target machine.
The JVM backend supports Castella UI, a reactive GUI framework based on Skia rendering.
git clone https://github.com/i2y/konpeito.git
cd konpeito/examples/castella_ui
bash setup.sh # downloads JWM + Skija JARs (~30 MB, one-time)
bash run.sh framework_counter.rb# framework_counter.rb
class CounterApp < Component
def initialize
super
@count = state(0)
end
def view
label = "Count: " + @count.value.to_s
Column(
Text(label).font_size(32.0),
Row(
Button(" - ").on_click { @count -= 1 },
Button(" + ").on_click { @count += 1 }
).spacing(8.0)
)
end
end
$theme = theme_tokyo_night
frame = JWMFrame.new("Counter", 400, 300)
app = App.new(frame, CounterApp.new)
app.runSee Getting Started for the full Castella UI widget catalog and theme list.
The mruby backend produces standalone executables — no Ruby or Java required on the target machine. This is ideal for distributing applications, writing games, or deploying to environments without a Ruby installation.
# hello.rb
def main
puts "Hello from Konpeito!"
end
mainkonpeito build --target mruby -o hello hello.rb
./hello # => Hello from Konpeito!Or build and run in one step:
konpeito run --target mruby hello.rbKonpeito includes a raylib stdlib for the mruby backend. Reference module Raylib in your code and the compiler auto-detects the RBS/C wrapper — no manual setup needed.
# catch_game.rb
module Raylib
end
def main
screen_w = 600
screen_h = 400
Raylib.init_window(screen_w, screen_h, "Catch Game")
Raylib.set_target_fps(60)
paddle_x = screen_w / 2 - 40
paddle_y = screen_h - 40
paddle_speed = 400
obj_x = Raylib.get_random_value(30, screen_w - 30)
obj_y = 0
obj_speed = 150.0
score = 0
while Raylib.window_should_close == 0
dt = Raylib.get_frame_time
if Raylib.key_down?(Raylib.key_left) != 0
paddle_x = paddle_x - (paddle_speed * dt).to_i
end
if Raylib.key_down?(Raylib.key_right) != 0
paddle_x = paddle_x + (paddle_speed * dt).to_i
end
obj_y = obj_y + (obj_speed * dt).to_i
if obj_y > screen_h
obj_x = Raylib.get_random_value(30, screen_w - 30)
obj_y = 0
end
Raylib.begin_drawing
Raylib.clear_background(Raylib.color_black)
Raylib.draw_rectangle(paddle_x, paddle_y, 80, 14, Raylib.color_skyblue)
Raylib.draw_rectangle(obj_x, obj_y, 16, 16, Raylib.color_gold)
Raylib.draw_text("Score: #{score}", 10, 10, 20, Raylib.color_white)
Raylib.end_drawing
end
Raylib.close_window
end
mainkonpeito run --target mruby catch_game.rbKonpeito includes a Clay UI stdlib for building Flexbox-style layouts. Clay computes layout from a tree of containers and outputs draw commands rendered via raylib. Reference module Clay in your code for auto-detection.
# Clay layout: open/close containers, set layout/bg, add text
Clay.begin_layout
Clay.open("root")
Clay.layout(1, 16, 16, 16, 16, 8, 1, 0.0, 1, 0.0, 2, 0) # vertical, grow, center-x
Clay.bg(40.0, 40.0, 60.0, 255.0, 0.0)
Clay.open("card")
Clay.layout(1, 12, 12, 12, 12, 4, 1, 0.0, 0, 0.0, 0, 0) # vertical, grow-w, fit-h
Clay.bg(255.0, 255.0, 255.0, 255.0, 8.0) # white, rounded
Clay.border(200.0, 200.0, 200.0, 255.0, 1, 1, 1, 1, 8.0) # gray border
Clay.text("Hello!", font, 24, 40.0, 40.0, 40.0, 255.0, 0)
Clay.close
Clay.close
Clay.end_layout
Clay.render_raylib # render all commands via raylibKey concepts:
Clay.open(id)/Clay.close— define nested containersClay.layout(dir, pl, pr, pt, pb, gap, sw_type, sw_val, sh_type, sh_val, ax, ay)— Flexbox layoutClay.bg(r, g, b, a, corner_radius)— background color with rounded cornersClay.border(r, g, b, a, top, right, bottom, left, corner_radius)— borderClay.text(str, font_id, size, r, g, b, a, wrap)— text elementClay.pointer_over(id)/Clay.pointer_over_i(id, index)— hit testing
See examples/mruby_clay_ui/ for a full sidebar layout demo and a Memory Match card game.
Konpeito includes a game framework (game_framework.rb) with helpers for building 2D games:
- Tilemap rendering, sprite animation, scene management, NPC system, text boxes
- Tween/easing (linear, quad, cubic, bounce, elastic), screen shake, scene transitions
- Simple physics (AABB, gravity, friction), particle system, object pool
- FSM (finite state machine), timer system, parallax scrolling
- Grid/tile utilities, debug overlay (FPS, collision rects), gamepad abstraction, save/load
- Clay UI helpers:
fw_clay_rpg_window,fw_clay_bar,fw_clay_num,fw_clay_menu_item - Uses
module GwithNativeArrayglobals for zero-allocation game state
See examples/mruby_dq_rpg/ for a JRPG-style demo and examples/game_showcase/ for a platformer demo showcasing physics, particles, tween, FSM, and parallax.
KUI (Konpeito UI) is a pure Ruby immediate-mode UI DSL. Unlike retained-mode frameworks where you build a persistent widget tree, KUI rebuilds the entire UI every frame from function calls. State lives in your own variables — there are no framework-managed widget objects.
# Every frame: call functions to describe the UI
def draw
vpanel pad: 16, gap: 8 do
label "Count: ", size: 20
label_num $count, size: 20
button "Add" do $count = $count + 1 end
end
endKUI wraps Clay + Raylib (GUI) or ClayTUI + termbox2 (TUI) behind a unified API. Write your UI once, build for either backend.
# counter_gui.rb
require "kui_gui"
$count = 0
def draw
vpanel pad: 24, gap: 16 do
header pad: 12 do
label "Counter App", size: 28, r: 255, g: 255, b: 255
end
spacer
cpanel gap: 8 do
label "Current Count:", size: 20
hpanel gap: 4 do
spacer
label_num $count, size: 36, r: 100, g: 200, b: 255
spacer
end
end
spacer
hpanel gap: 12 do
spacer
button " - ", size: 20 do
$count = $count - 1
end
button " Reset ", size: 20 do
$count = 0
end
button " + ", size: 20 do
$count = $count + 1
end
spacer
end
spacer
divider
footer do
label "KUI Framework Demo", size: 14, r: 120, g: 120, b: 140
end
end
return 0
end
def main
kui_init("KUI Counter", 450, 350)
kui_theme_dark
while kui_running == 1
kui_begin_frame
draw
kui_end_frame
end
kui_destroy
return 0
end
mainkonpeito build --target mruby -o counter_gui counter_gui.rb
./counter_guiTo build the same app for the terminal, change only the require line:
# counter_tui.rb
require "kui_tui"
# ... same draw/main code ...
# Add _kui_update_focus at end of frame for keyboard navigationkonpeito build --target mruby -o counter_tui counter_tui.rb
./counter_tui| Widget | Description |
|---|---|
vpanel(pad:, gap:) { } |
Vertical container (GROW width/height) |
hpanel(pad:, gap:) { } |
Horizontal container (GROW width, FIT height) |
row(gap:) { } |
Horizontal container with GROW height (like Castella's row) |
cpanel(pad:, gap:) { } |
Centered container |
fixed_panel(w, h, pad:) { } |
Fixed-size container |
pct_panel(wpct, hpct:, pad:, gap:) { } |
Percentage-width container (0-100) |
scroll_panel(pad:, gap:) { } |
Scrollable vertical container |
card(pad:, gap:) { } |
Surface panel with border |
header(pad:) { } |
Top bar with primary color |
footer(pad:) { } |
Bottom bar with subtle color |
sidebar(w, pad:, gap:) { } |
Fixed-width vertical sidebar |
label(text, size:, r:, g:, b:) |
Text label |
label_num(value, size:, r:, g:, b:) |
Integer display (no string allocation) |
button(text, size:, kind:, flex:, style:) { } |
Clickable button with color variants and flex sizing |
menu_item(text, index, cursor, size:) |
Selectable menu item |
spacer |
Fills available space |
divider(r:, g:, b:) |
Horizontal line |
progress_bar(value, max, w, h, r:, g:, b:) |
Progress bar |
Buttons support semantic color variants and flex-based sizing, similar to Castella's Style system:
# Kind: semantic color (KUI_KIND_DEFAULT/INFO/SUCCESS/WARNING/DANGER)
button "Save", kind: KUI_KIND_SUCCESS do save_data end
button "Delete", kind: KUI_KIND_DANGER do delete_item end
# Flex: percentage of parent width (use inside row)
row gap: 4 do
button "Wide", flex: 75 do end # 75% width
button "Narrow", flex: 25 do end # 25% width
endCompose reusable styles with kui_style and kui_style_merge, similar to Castella's Style.new and +:
# Define base style
btn = kui_style(size: 32, flex: 25)
# Compose variants (non-zero values override)
op = kui_style_merge(btn, kui_style(kind: KUI_KIND_WARNING))
ac = kui_style_merge(btn, kui_style(kind: KUI_KIND_DANGER, flex: 75))
eq = kui_style_merge(btn, kui_style(kind: KUI_KIND_SUCCESS))
# Use with button
row gap: 4 do
button "AC", style: ac do all_clear end
button "/", style: op do press_operator(OP_DIV) end
endkui_init("Title", width, height) # Initialize window/terminal
# 7 theme presets available:
kui_theme_dark # Dark (default)
kui_theme_light # Light
kui_theme_tokyo_night # Tokyo Night (Castella default)
kui_theme_nord # Nord (arctic blue)
kui_theme_dracula # Dracula (vibrant dark)
kui_theme_catppuccin # Catppuccin Mocha (pastel)
kui_theme_material # Material Design (light)
while kui_running == 1
kui_begin_frame
# ... draw UI ...
kui_end_frame
end
kui_destroy # CleanupSet background color using theme-aware named functions instead of raw KUITheme.c[n]:
card do
kui_bg_surface # theme surface color
kui_bg_surface2 # secondary surface
kui_bg_primary # primary accent
kui_bg_info # informational blue
kui_bg_success # success green
kui_bg_warning # warning orange
kui_bg_danger # danger red
kui_bg_accent # accent color
endkey = kui_key_pressed
# Returns: KUI_KEY_UP, KUI_KEY_DOWN, KUI_KEY_LEFT, KUI_KEY_RIGHT,
# KUI_KEY_ENTER, KUI_KEY_ESC, KUI_KEY_SPACE, KUI_KEY_TAB,
# KUI_KEY_BACKSPACE, or KUI_KEY_NONEKUI apps can use Ruby global variables for state:
$score = 0
$player_name = "Alice"
button "Add Point" do
$score = $score + 1
endFor performance-critical apps (games with many state variables), you can use module NativeArray for GC-free state:
# @rbs module G
# @rbs @s: NativeArray[Integer, 64]
# @rbs end
G.s[0] = 42 # write
x = G.s[0] # readSee examples/kui_counter/ for counter demos, examples/kui_dashboard/ for a multi-page dashboard, examples/kui_calc/ for a Castella-style calculator, and examples/kui_cafe/ for a full management simulation game using 20+ widgets.
Execute shell commands and read/write files:
# Auto-detected when KonpeitoShell is referenced
output = KonpeitoShell.exec("ls -la")
status = KonpeitoShell.exec_status # 0 = success
home = KonpeitoShell.getenv("HOME")
KonpeitoShell.setenv("MY_VAR", "value")
content = KonpeitoShell.read_file("data.txt")
KonpeitoShell.write_file("out.txt", "hello")
KonpeitoShell.append_file("log.txt", "new line\n")
if KonpeitoShell.file_exists("config.json") == 1
# ...
endParse and generate JSON using yyjson:
obj = KonpeitoJSON.parse('{"name": "Alice", "age": 30}')
json = KonpeitoJSON.generate(obj)HTTP client using libcurl:
body = KonpeitoHTTP.get("https://example.com")Hashing and encryption using OpenSSL:
hash = KonpeitoCrypto.sha256("hello")Data compression using zlib:
compressed = KonpeitoCompression.gzip("Hello, World!")
decompressed = KonpeitoCompression.gunzip(compressed)All stdlib modules above are auto-detected — just reference the module name in your code and the compiler will inject the appropriate RBS and C wrappers.
Cross-compile for other platforms using zig cc:
konpeito build --target mruby \
--cross aarch64-linux-musl \
--cross-mruby ~/mruby-aarch64 \
-o game game.rb| Option | Description |
|---|---|
--cross TARGET |
Target triple (e.g., x86_64-linux-gnu) |
--cross-mruby DIR |
Cross-compiled mruby install path |
--cross-libs DIR |
Additional library search path |
| Aspect | CRuby backend | mruby backend |
|---|---|---|
| Output | .so/.bundle (extension) | Standalone executable |
| Runtime dependency | CRuby 4.0+ | None |
| Use case | Library/app acceleration | Distribution, games |
| raylib stdlib | Not available | Auto-detected |
| Clay UI stdlib | Not available | Auto-detected |
| KUI (declarative UI) | Not available | Auto-detected (GUI/TUI) |
| Thread/Mutex | Supported | Not supported |
| Keyword arguments | Supported | Supported |
| Compilation caching | .konpeito_cache/run/ |
.konpeito_cache/run/ |
Konpeito infers types automatically using Hindley-Milner inference:
def double(x)
x * 2 # 2 is Integer → x is Integer → return is Integer
end
def greet(name)
"Hello, " + name # String + String → String
endInferred types are used directly for unboxed optimizations. No RBS needed.
RBS gives the compiler more precise information:
Separate file:
# sig/math.rbs
module TopLevel
def add: (Integer a, Integer b) -> Integer
endkonpeito build --rbs sig/math.rbs math.rbInline (rbs-inline):
# rbs_inline: enabled
#: (Integer, Integer) -> Integer
def add(a, b)
a + b
endkonpeito build --inline math.rbTyped high-performance data structures are available (CRuby and mruby backends):
| Type | Use case | Characteristics |
|---|---|---|
NativeArray[T] |
Numeric arrays | Unboxed, contiguous memory, 5-15x faster |
NativeClass |
Structs | Unboxed fields, 10-20x faster |
StaticArray[T, N] |
Fixed-size arrays | Stack-allocated, no GC pressure |
NativeHash[K, V] |
Hash maps | Linear probing, 4x faster |
Slice[T] |
Memory views | Zero-copy, bounds-checked |
# NativeArray example
def sum_array(n)
arr = NativeArray.new(n)
i = 0
while i < n
arr[i] = i * 1.5 # unboxed store
i = i + 1
end
total = 0.0
i = 0
while i < n
total = total + arr[i] # unboxed load
i = i + 1
end
total
endThese types require RBS definitions. See the API Reference for details.
konpeito init scaffolds a new project:
konpeito init --target jvm my_app
cd my_appmy_app/
konpeito.toml # build configuration
src/
main.rb # entry point
test/
main_test.rb # test stub
lib/ # JVM dependencies (JARs)
.gitignore
konpeito run src/main.rb # compile & run
konpeito test # run testskonpeito build -v source.rb # show inferred types and dynamic fallback warnings
konpeito build -g source.rb # emit DWARF debug info for lldb/gdb (LLVM backend)
konpeito check source.rb # type-check only (no code generation)
konpeito build --profile source.rb # build with profiling instrumentation
konpeito run --no-cache source.rb # force recompilation (skip run cache)
konpeito run --clean-run-cache source.rb # clear run cache, then build and run
konpeito fmt # format code (RuboCop)
konpeito deps source.rb # analyze and display dependencies
# mruby backend
konpeito build --target mruby -o app app.rb # standalone executable
konpeito run --target mruby app.rb # build & run (cached)
konpeito doctor --target mruby # check mruby environment- CLI Reference — All commands and options
- API Reference — Native data structures, standard library, and Castella UI widgets
- Language Specification — Supported syntax and type system rules