RBS File Requirements Guide
Konpeito can compile and run many programs without RBS files thanks to HM type inference (Algorithm W). This document explains when RBS is required and when it is not.
Cases Where RBS is Not Required
1. When Types Can Be Inferred from Literals
HM type inference can reverse-infer argument and variable types from literal values.
# Works - 10 is Integer, so a and b are inferred as Integer
def add(a, b)
a + b + 10
end
# Works - String#+ implies name is String
def greet(name)
"Hello, " + name
end
# Works - 1.5 is Float, so x is inferred as Float
def scale(x)
x * 1.5
end
2. When Types Can Be Inferred from Call Sites
Types are inferred from function call sites, and monomorphization generates concrete code for each specific type.
# Works - inferred from call sites
def identity(x)
x
end
identity(42) # identity_Integer is generated
identity("hello") # identity_String is generated
identity(3.14) # identity_Float is generated
3. When Using Methods of Built-in Types
The RBS loader always loads RBS definitions for Ruby standard built-in types (Integer, Float, String, Array, Hash, etc.). Methods of these types can be used without explicit RBS.
# Works - built-in type methods
arr = [1, 2, 3] # Array[Integer]
doubled = arr.map { |x| x * 2 } # Array#map is known
sum = arr.reduce(0) { |s, x| s + x } # Array#reduce is known
str = "hello"
len = str.length # String#length is known
upper = str.upcase # String#upcase is known
hash = { a: 1, b: 2 }
hash[:a] # Hash#[] is known
4. Control Structures and Basic Syntax
Control structures such as if/unless, while, case/when work without type annotations.
# Works
def factorial(n)
if n <= 1
1
else
n * factorial(n - 1)
end
end
# Works
def sum_to(n)
total = 0
i = 1
while i <= n
total = total + i
i = i + 1
end
total
end
# Works
def classify(x)
case x
when 0 then "zero"
when 1..10 then "small"
else "large"
end
end
5. Blocks and Iterators
# Works
def each_squared(arr)
arr.each { |x| puts x * x }
end
# Works
def select_positive(arr)
arr.select { |x| x > 0 }
end
Cases Where RBS is Required
1. NativeClass (Native Struct)
To treat a class as a native struct, you must define the field layout in RBS.
# types.rbs - Required
class Point
@x: Float
@y: Float
def self.new: () -> Point
def x: () -> Float
def x=: (Float) -> Float
def y: () -> Float
def y=: (Float) -> Float
end
# point.rb
class Point
def x = @x
def x=(v) = @x = v
def y = @y
def y=(v) = @y = v
end
Without RBS, the class is treated as a regular Ruby object (VALUE type), and unboxed optimizations are not applied.
2. NativeArray
RBS is required to specify the element type of a NativeArray.
Local NativeArray (function-scoped, dynamic size):
# types.rbs - Required
class NativeArray[T]
def self.new: (Integer size) -> NativeArray[Float]
def []: (Integer index) -> Float
def []=: (Integer index, Float value) -> Float
def length: () -> Integer
end
Module NativeArray (global, fixed size, cross-function):
# types.rbs - Required
module GameState
@positions: NativeArray[Integer, 100]
@velocities: NativeArray[Float, 100]
end
Module NativeArray uses the syntax NativeArray[T, N] where T is Integer or Float and N is the fixed size. Access via GameState.positions[i] / GameState.positions[i] = v. Available on LLVM (CRuby) and mruby backends.
3. %a{cfunc}/%a{ffi} (External C Library Integration)
To call external C functions directly, you must specify annotations in RBS.
# math_lib.rbs - Required
%a{ffi: "libm"}
module MathLib
%a{cfunc}
def self.sin: (Float) -> Float
%a{cfunc: "sqrt"}
def self.square_root: (Float) -> Float
end
4. %a{simd} (SIMD Vectorization)
To use SIMD vector types, you must define annotations and fields in RBS.
# vector.rbs - Required
%a{simd}
class Vector4
@x: Float
@y: Float
@z: Float
@w: Float
def self.new: () -> Vector4
def add: (Vector4 other) -> Vector4
def dot: (Vector4 other) -> Float
end
5. %a{extern} (External C Struct Wrapper)
RBS is required to wrap structs from external C libraries.
# sqlite.rbs - Required
%a{ffi: "libsqlite3"}
%a{extern}
class SQLiteDB
def self.open: (String path) -> SQLiteDB
def execute: (String sql) -> Array
def close: () -> void
end
6. When Explicit Type Constraints Are Needed
RBS is used when type inference alone is insufficient and you want to enforce specific types.
# types.rbs - Optional but recommended
module TopLevel
# Guarantee that the argument is always Integer
def compute: (Integer n) -> Integer
end
Fallback Behavior
When there is no RBS and type inference is not possible, Konpeito falls back to VALUE type (Ruby object).
# Falls back to VALUE type (works but is not optimized)
def mystery(x)
x.unknown_method # type of unknown_method is unknown
end
In this case:
- The code can be compiled and executed
- Method calls go through
rb_funcallv - Optimizations such as unboxed arithmetic are not applied
Decision Flowchart
Want to compile code
|
v
Using NativeClass/NativeArray?
|
+-- Yes -> RBS required (field layout definition)
|
v
Using @cfunc/@ffi/extern/simd?
|
+-- Yes -> RBS required (annotation needed)
|
v
Can types be determined by inference?
|
+-- Yes -> RBS not required (works with HM inference)
|
v
Is VALUE type fallback acceptable?
|
+-- Yes -> RBS not required (works without optimization)
|
v
RBS recommended (enable optimization with explicit types)
Summary
| Situation | RBS | Notes |
|---|---|---|
| Pure computation and loops | Not required | Types determined by HM inference |
| Built-in type operations | Not required | Standard RBS loaded automatically |
| NativeClass | Required | Field layout definition |
| NativeArray | Required | Element type specification |
| StaticArray | Required | Size and element type specification |
| Slice | Required | Element type specification |
| %a{cfunc}/%a{ffi} | Required | Annotation needed |
| %a{simd} | Required | Annotation needed |
| %a{extern} | Required | Annotation needed |
| When types cannot be inferred | Recommended | Works with VALUE type fallback |
| Explicitly enabling optimization | Recommended | Guarantees unboxed arithmetic |