Ruby has long been praised for its flexibility and expressiveness, making it a favorite among developers who value rapid prototyping and ease of use. However, this dynamic nature has also brought challenges, particularly in maintaining large codebases where bugs and inconsistencies can slip through undetected. Over the years, the Ruby community has recognized the need for tools to bring more predictability and safety to Ruby code without sacrificing its dynamic essence. This is how static typing came into play.
Stripe was the first to introduce type-checking with its Sorbet gem. Later, the Ruby team officially picked up this topic as well. Since that time, there have been two available solutions for static typing. Both are great, and both come with their pros and cons.
Today, I am not going to discuss Sorbet, a great tool used by many companies, including Shopify. Instead, I will provide an overview of the official Ruby solution as of 2025.
RBS, or Ruby Signature, is at the heart of Ruby’s static typing system. It lets developers define type signatures for their Ruby code without altering its runtime behavior. By providing a formal way to describe the structure of Ruby code, RBS helps developers:
- Catch type errors early in the development process.
- Improve documentation with clear type annotations.
Over the years, RBS has seen numerous enhancements, including better support for complex data structures, generics, and more comprehensive integration with Ruby’s core libraries. This evolution has made it an indispensable tool for developers seeking to bring more rigor to their Ruby projects.
While RBS provides the foundation for static typing, one requires a tool to do the actual type checking - Steep. Developed as a companion tool to RBS, Steep analyzes Ruby code against its type signatures to identify potential type mismatches and other issues.
Steep allows Ruby developers to:
- Gradually adopt static typing in existing codebases.
- Ensure consistency between type signatures and actual code.
- Catch subtle bugs that might otherwise go unnoticed.
Its integration with CI/CD pipelines has made it easy for teams to enforce type safety standards, contributing to the overall reliability and maintainability of their codebases.
To sum up, we need 2 components to ensure the proper static type checking in Ruby: RBS
and Steep
. Those are enough for you to start, though one may want to get the benefits of modern IDEs like Visual Studio Code (with some additional extensions) or RubyMine (which supports RBS out of the box).
Personally, I have been working with Ruby for more than a decade and have always been missing something that would describe the code better than my test suites. RBS can finally give me that. It helps not just check that, let's say, the age
parameter passed to a method must be an Integer
, it also serves as perfect documentation, which is always up to date.
Getting into RBS and type-checking requires one to change one's mindset. From "Let's write some dirty quick script first and iterate later if possible" to "Let's write it once and it will work forever". Not literally, but quite closer to it. The feeling of this is similar to adopting TDD or Rubocop (when it just came to the market). You have to think a little bit differently and accept that you will proceed slowly, especially at the beginning.
Once you get familiar with RBS' syntax, the ecosystem, with all the principles around it, you will be surprised how fast you will go. It is such a great feeling of confidence with your code. Something that you cannot get by covering every bit of your code with tests. Add IDE support here and you will never want to switch back.
Enough of words, let's briefly set things up and check how it works. As I mentioned, we need 2 gems rbs
and steep
. So let's install them with gem install rbs steep
Once you have done this, we need to initialize Steep with steep init
. This command will generate a Steepfile
- a configuration file that will tell Steep how to check your code. We do not need much of it not, but let's open it and replace the content with:
target :app do
signature "sig"
check "func.rb"
end
This will tell Steep to check just our ruby script and get signatures from the sig
folder. We do not need anything else for this example. Let's add the missing parts. First of all, we need a new folder - sig
, where we will store the signatures for our code.
Next, I will create 2 files, the first one is func.rb
:
def factorial(n)
return 1 if n <= 1
n * factorial(n - 1)
end
puts factorial(1)
puts factorial("1")
and then the corresponding signature file for it in sig/func.rbs
:
class Object
def factorial: (Integer n) -> Integer
end
Now, when I run steep check
I will get the output:
# Type checking files:
.F
func.rb:8:15: [error] Cannot pass a value of type `::String` as an argument of type `::Integer`
│ ::String <: ::Integer
│ ::Object <: ::Integer
│ ::BasicObject <: ::Integer
│
│ Diagnostic ID: Ruby::ArgumentTypeMismatch
│
└ puts factorial("1")
~~~
Which tells us that we pass the value of the wrong type to our function. Even without running any single test, Steep allows us to prevent lots of errors and make the software more robust.
Over time, you will get a habit of writing signature files first and implementing them in Ruby code later. Similar to how you would do it in vanilla TDD when you first write the test and then the corresponding logic. You still have to work on test coverage, but with RBS, you get an extra safety layer.
To sum up, the adoption of static typing in Ruby has brought numerous benefits to developers and organizations alike:
- Improved Code Quality: Type annotations act as a form of documentation, making code easier to understand and maintain.
- Early Bug Detection: Static type checking catches errors at compile time, reducing the likelihood of runtime issues.
- Enhanced Tooling Support: IDEs and editors now offer better features like intelligent autocomplete and error highlighting, powered by RBS.
- Easier Collaboration: In team environments, type annotations provide a shared understanding of data structures and interfaces.
- Scalability: Static typing adds predictability to large projects, making them easier to scale and refactor.
There are many other features, that I am not going to cover much in the blog post. Right now, RBS and Co give you the full coverage of Ruby's standard library right out of the box plus a bunch of popular gems including Ruby on Rails. There are ways of automatically generating signatures for your code (which does not work super precisely yet, but it is a good starting point for working with legacy software) and even writing the signatures in line with your code.
So far, I have described only positive or easy things about static typing in Ruby. Is this really that easy and straightforward, after all? The simplest answer would be yes, but © there are, of course, some nuances that one should consider.
Apart from the mindset change, what I already mentioned, the dev. experience is not yet super polished enough. Today, you get two gems that let you write signatures for your code and ensure that everything is correct by doing type-checking. You will also get IDE support (whether it is RubyMine or VS Code). However, as all those parts are being actively developed, which means there may be cases when something stops working for a while (until it's fixed).
If you check the web for similar blog posts but from previous years, you will see how significantly the whole ecosystem around static type checking has evolved. It is now not just a bunch of experiments but a rather stable production-ready set of tools, yet with some small risks to keep in mind.
The ecosystem around static typing is rapidly evolving in Ruby. Whether it is RBS or Sorbet, you can get all the necessary features and documentation right away. The wait is over. The switch seems hard but only from the first look. Once you accept the mindset change, the rest will be just a matter of time.
Personally, for me, that was a game changer for the projects I work on. It gives me so much confidence in how the code works. I started adopting static typing when it was just released back in 2020 and had to figure out many solutions on my own or wait until someone else found the proper way. You, however, now may take a shortcut and learn everything you need around RBS in just a matter of several hours with my video course.
There's a growing need to raise awareness about static typing in Ruby—a powerful tool still waiting to be embraced by Ruby developers worldwide. That's why I publish a monthly newsletter covering RBS and Sorbet. Jump in!
Top comments (0)