Learn Ruby - Type System: RBS, Sorbet & Steep
Series/Learn Ruby/Episode 18
Episode 18 of 23

Learn Ruby - Type System: RBS, Sorbet & Steep

This episode introduces type systems for Ruby: writing RBS signatures in .rbs files and checking them with Steep, plus Sorbet with gradual typing, sorbet-typed, and runtime integration for more reliable code in production.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Ruby is a dynamically typed language, but in large projects, a typo in a method name or a wrong argument type is only caught at runtime. Episode 18 introduces type systems you can layer on top of Ruby: RBS with the static type checker Steep, and Sorbet with gradual typing.

Neither approach forces you to annotate types everywhere at once. You start from critical files or methods, and the checker gradually turns on. The result: fewer bugs in production and automatically readable type documentation. You choose one tool based on your needs, not both at the same time.

Types are not a replacement for tests — the two catch different categories of bugs. Types check data contracts statically, while tests verify actual behavior. Use both side by side for the best results.

RBS: Signature Type

Writing .rbs Files

RBS is the official type description language from the Ruby team. Signatures are written in .rbs files separate from the code — describing methods, arguments, and return values:

RubySignature RBS untuk Kalkulator
class Kalkulator
  def tambah: (Integer a, Integer b) -> Integer
  def bagi: (Integer a, Integer b) -> Float | nil
  attr_accessor nama: String
end

def tambah: (Integer a, Integer b) -> Integer declares that the tambah method takes two Integers and returns an Integer. Float | nil marks a union type — the method can return a Float or nil. RBS also supports generics, interfaces, and untyped for a transitional period. Signature files are typically placed in the sig/ directory, structured to mirror their classes.

Managing Signatures from Gems

You don't have to write signatures yourself. Many gems ship .rbs files inside their packages, and rbs collection helps gather them all in one place:

Mengelola signature gem
rbs collection init
rbs collection install

rbs collection init creates the collection configuration file, then rbs collection install downloads the available gem signatures. This way the type checker understands third-party code without you retyping thousands of lines of signatures.

To speed things up, rbs prototype rb can generate an initial signature from existing Ruby code, and rbs validate checks the correctness of signature file syntax. These two commands are invaluable when adopting RBS in an already-running codebase.

Steep: Static Type Checker

Steep checks code against RBS signatures without running the program. Install and run it from the terminal:

Memeriksa tipe dengan Steep
gem install steep
steep init
steep check

steep check reads the signatures in sig/ and reports mismatches: arguments of the wrong type, returns that don't match the contract, or calls to methods that don't exist. The checker is configured through a Steepfile — specify the code path, signature path, and the libraries in use. Run Steep in CI so type regressions are caught before they break production.

Steep also works in the editor through the Language Server Protocol. With this setup, type feedback appears as you type, instead of waiting for the checker's terminal output. Combining editor and CI makes typing a natural part of the workflow.

Sorbet: Gradual Typing

Sigil and Signature

Sorbet from Stripe works differently: types are written directly inside the Ruby file via a sigil comment on the first line and annotations:

RubySorbet dengan signature
# typed: true
 
class Kalkulator
  extend T::Sig
 
  sig { params(a: Integer, b: Integer).returns(Integer) }
  def tambah(a, b)
    a + b
  end
end

# typed: true enables type checking for that file. sig { params(...).returns(...) } declares the method contract. Sorbet also supports the strict and strong levels for files that are fully typed, so adoption can happen file by file without rewriting the whole codebase at once.

The strict level requires every method to have a signature, while strong tightens type enforcement overall. Teams usually raise the level gradually as code gets annotated, starting from false, then true, strict, and finally strong.

sorbet-typed and Runtime Integration

Sorbet provides the sorbet-typed repository containing signatures for popular gems, so the checker can understand third-party code. Sorbet also runs at runtime: if a signature is violated in production, an exception is raised according to policy. Set up a project with these commands:

Mengaktifkan Sorbet
bundle add sorbet sorbet-runtime
srb init
srb rbi sorbet-typed
srb tc

srb init creates the configuration skeleton and RBI files. srb rbi sorbet-typed imports signatures from the sorbet-typed repository, while srb tc runs type checking. For runtime, the library is available as sorbet-runtime. Sorbet offers an attractive compromise: type rigor when you need it, without changing Ruby itself.

Tip

Choose the tool that fits your needs: RBS is the official standard and more gems provide signatures for it; Sorbet excels at gradual adoption in large codebases with built-in runtime checking.

Conclusion

Key takeaways:

  • RBS describes types in separate .rbs files.
  • def nama: (Tipe a) -> Tipe declares arguments and return.
  • rbs collection install gathers gem signatures automatically.
  • Steep checks code against signatures without running the program.
  • Sorbet uses the # typed: sigil and sig annotations.
  • sorbet-typed brings signatures for popular gems into the checker.
  • Sorbet can also check types at runtime in production.

In episode 19 we open Ruby's hidden door: metaprogramming with send, define_method, method_missing, and class_eval to build DSLs, then understand the singleton class and the method lookup chain. With types secure, we explore the magic of Ruby's dynamism.