Skip to main content
Examples Verified (100%)

RBS Integration

T-Ruby seamlessly integrates with RBS (Ruby Signature), Ruby's official type signature format. When you compile T-Ruby code, the compiler automatically generates .rbs files alongside the Ruby output, enabling integration with the broader Ruby typing ecosystem.

What is RBS?​

RBS is Ruby's standard format for type signatures. It's a separate language for describing the structure of Ruby programs, including:

  • Method signatures
  • Class and module definitions
  • Instance and class variables
  • Generics and type parameters

How T-Ruby Generates RBS​

When you compile T-Ruby code, the compiler extracts type information and generates corresponding RBS files.

Basic Example​

T-Ruby input (user.trb):

class User
@id: Integer
@name: String
@email: String

def initialize(id: Integer, name: String, email: String): void
@id = id
@name = name
@email = email
end

def greet: String
"Hello, I'm #{@name}!"
end

def update_email(new_email: String): void
@email = new_email
end
end

Generated RBS (sig/user.rbs):

class User
@id: Integer
@name: String
@email: String

def initialize: (Integer id, String name, String email) -> void
def greet: () -> String
def update_email: (String new_email) -> void
end

Generated Ruby (build/user.rb):

class User
def initialize(id, name, email)
@id = id
@name = name
@email = email
end

def greet
"Hello, I'm #{@name}!"
end

def update_email(new_email)
@email = new_email
end
end

Compilation Options​

Enable/Disable RBS Generation​

By default, RBS files are generated. You can control this:

trbconfig.yml
compiler:
generate_rbs: true # Default

Or via command line:

# Skip RBS generation
trc compile --no-rbs src/

# Generate only RBS (skip Ruby)
trc compile --rbs-only src/

RBS Output Directory​

Configure where RBS files are written:

trbconfig.yml
output:
rbs_dir: sig # Default
trc compile --rbs-dir signatures/ src/

RBS Features Supported​

Method Signatures​

T-Ruby automatically generates method signatures with parameters and return types.

calculator.trb
def add(a: Integer, b: Integer): Integer
a + b
end

def divide(a: Float, b: Float): Float | nil
return nil if b == 0
a / b
end
sig/calculator.rbs
def add: (Integer a, Integer b) -> Integer
def divide: (Float a, Float b) -> (Float | nil)

Optional and Keyword Parameters​

formatter.trb
def format(
text: String,
uppercase: Boolean = false,
prefix: String? = nil
): String
result = uppercase ? text.upcase : text
prefix ? "#{prefix}#{result}" : result
end
sig/formatter.rbs
def format: (
String text,
?Boolean uppercase,
?String? prefix
) -> String

Block Signatures​

iterator.trb
def each_item(items: String[]): void do |String| -> void end
items.each { |item| yield item }
end
sig/iterator.rbs
def each_item: (Array[String] items) { (String) -> void } -> void

Generics​

T-Ruby's generic types map directly to RBS generics.

container.trb
class Container<T>
@value: T

def initialize(value: T): void
@value = value
end

def get: T
@value
end

def set(value: T): void
@value = value
end
end
sig/container.rbs
class Container[T]
@value: T

def initialize: (T value) -> void
def get: () -> T
def set: (T value) -> void
end

Union Types​

parser.trb
def parse(input: String): Integer | Float | nil
return nil if input.empty?

if input.include?(".")
input.to_f
else
input.to_i
end
end
sig/parser.rbs
def parse: (String input) -> (Integer | Float | nil)

Modules and Mixins​

loggable.trb
module Loggable
def log(message: String): void
puts "[LOG] #{message}"
end

def log_error(error: String): void
puts "[ERROR] #{error}"
end
end

class Service
include Loggable

def process: void
log("Processing...")
end
end
sig/loggable.rbs
module Loggable
def log: (String message) -> void
def log_error: (String error) -> void
end

class Service
include Loggable

def process: () -> void
end

Type Aliases​

types.trb
type UserId = Integer
type UserMap = Hash<UserId, User>

def find_users(ids: UserId[]): UserMap
# ...
end
sig/types.rbs
type UserId = Integer
type UserMap = Hash[UserId, User]

def find_users: (Array[UserId] ids) -> UserMap

Interfaces​

T-Ruby interfaces are converted to RBS interface types.

printable.trb
interface Printable
def to_s: String
def print: void
end

class Document
implements Printable

def to_s: String
"Document"
end

def print: void
puts to_s
end
end
sig/printable.rbs
interface _Printable
def to_s: () -> String
def print: () -> void
end

class Document
include _Printable

def to_s: () -> String
def print: () -> void
end

Using Generated RBS Files​

With Steep​

Steep can use T-Ruby's generated RBS files for type checking:

Steepfile
target :app do
signature "sig" # T-Ruby generated signatures
check "build" # Compiled Ruby files
end
trc compile src/
steep check

With Ruby LSP​

Configure Ruby LSP to use T-Ruby's RBS files:

.vscode/settings.json
{
"rubyLsp.enabledFeatures": {
"diagnostics": true
},
"rubyLsp.typechecker": "steep",
"rubyLsp.rbs.path": "sig"
}

With Sorbet​

Generate Sorbet-compatible type stubs from RBS:

# Generate RBS files
trc compile --rbs-only src/

# Convert to Sorbet stubs
rbs-to-sorbet sig/ sorbet/rbi/

With Standard Gem​

Import RBS signatures into gems:

my_gem.gemspec
Gem::Specification.new do |spec|
spec.name = "my_gem"
spec.files = Dir["lib/**/*", "sig/**/*"]
spec.metadata["rbs_signatures"] = "sig"
end

Advanced RBS Generation​

Custom RBS Annotations​

Add RBS-specific annotations in comments:

service.trb
class Service
# @rbs_skip
def debug_method: void
# This method won't appear in RBS
end

# @rbs_override
# def custom_signature: (String) -> Integer
def custom_method(input: String): Integer
input.length
end
end

External RBS Integration​

Combine T-Ruby's generated RBS with hand-written RBS:

sig/
├── generated/ # T-Ruby generated
│ ├── user.rbs
│ └── service.rbs
└── manual/ # Hand-written
└── external.rbs
trbconfig.yml
output:
rbs_dir: sig/generated

types:
paths:
- sig/manual
- sig/generated

Merging RBS Files​

If you have existing RBS files:

# Generate new RBS
trc compile --rbs-only src/

# Merge with existing
rbs merge sig/generated/ sig/manual/ -o sig/merged/

RBS Validation​

Validate generated RBS files:

# Generate RBS
trc compile src/

# Validate with RBS
rbs validate --signature-path=sig/

T-Ruby ensures generated RBS is always valid, but validation is useful when:

  • Combining with hand-written RBS
  • Using external type definitions
  • Debugging type issues

RBS and Type Checking Flow​

Here's how T-Ruby's RBS integration fits into type checking:

┌─────────────┐
│ .trb files │
│ (T-Ruby) │
└──────┬──────┘
│
▼
┌────────┐
│ trc │ Compile
└───┬────┘
│
├──────────┐
▼ ▼
┌──────────┐ ┌──────────┐
│ .rb files│ │.rbs files│
│ (Ruby) │ │ (RBS) │
└─────┬────┘ └────┬─────┘
│ │
│ ▼
│ ┌──────────┐
│ │ Steep │ Type check
│ │ Ruby LSP │
│ └──────────┘
│
▼
┌──────────┐
│ Ruby │ Execute
│Interpreter│
└──────────┘

Practical Examples​

Example 1: Library Development​

Create a typed library with RBS:

lib/my_library.trb
module MyLibrary
class Client
@api_key: String
@endpoint: String

def initialize(api_key: String, endpoint: String = "https://api.example.com"): void
@api_key = api_key
@endpoint = endpoint
end

def get<T>(path: String, params: Hash<String, Any> = {}): T | nil
# Implementation
end

def post<T>(path: String, body: Hash<String, Any>): T
# Implementation
end
end
end

Compile:

trc compile lib/

Generated files:

lib/
├── my_library.rb # For runtime
sig/
└── my_library.rbs # For type checking and documentation

Users can now:

# Use in Ruby
require "my_library"
client = MyLibrary::Client.new("key123")

# Type check with Steep
# steep check uses sig/my_library.rbs

Example 2: Rails Application​

Use RBS with Rails models:

app/models/user.trb
class User < ApplicationRecord
@name: String
@email: String
@admin: Boolean

def self.find_by_email(email: String): User | nil
find_by(email: email)
end

def admin?: Boolean
@admin
end

def promote_to_admin: void
update!(admin: true)
end
end
trbconfig.yml
source:
include:
- app/models

output:
ruby_dir: app/models
rbs_dir: sig

Compile:

trc compile

Now Steep can check your Rails app:

Steepfile
target :app do
signature "sig"
check "app"

library "activerecord"
end

Example 3: Gem with Type Signatures​

Package RBS with your gem:

my_gem.gemspec
Gem::Specification.new do |spec|
spec.name = "my_typed_gem"
spec.version = "1.0.0"

spec.files = Dir[
"lib/**/*.rb",
"sig/**/*.rbs"
]

spec.metadata = {
"rbs_signatures" => "sig"
}
end

Users automatically get type information when using your gem.

Troubleshooting​

RBS Generation Fails​

If RBS generation fails:

# Check compilation with verbose output
trc compile --verbose src/

# Validate T-Ruby types first
trc check src/

# Generate RBS separately
trc compile --rbs-only src/

RBS Validation Errors​

If RBS validation fails:

# Check specific RBS file
rbs validate sig/user.rbs

# View generated RBS
cat sig/user.rbs

# Re-generate with debug
trc compile --log-level debug src/

Type Mismatches​

If types don't match between T-Ruby and RBS:

# Check what RBS was generated
trc compile --rbs-only --output-file - src/user.trb

# Use type tracing
trc compile --trace src/

Best Practices​

1. Keep RBS in Version Control​

git add sig/
git commit -m "Update RBS signatures"

RBS files are source code - commit them alongside Ruby files.

2. Validate RBS in CI​

.github/workflows/ci.yml
- name: Generate and Validate RBS
run: |
trc compile src/
rbs validate --signature-path=sig/

3. Document Public APIs​

RBS files serve as documentation. Ensure public APIs are well-typed:

# Good - clear public API
class Service
def process(data: String[]): Hash<String, Integer>
# ...
end

private

def internal_helper(x) # Private can be untyped
# ...
end
end

4. Use Type Aliases for Clarity​

type UserId = Integer
type ResponseData = Hash<String, Any>

def fetch_user(id: UserId): ResponseData
# ...
end

5. Combine with Hand-Written RBS​

Keep generated and manual RBS separate:

sig/
├── generated/ # From T-Ruby
└── manual/ # Hand-written

Next Steps​