An Exploration of RBS by Ruby: Is it Production-Ready?
Is RBS - a type annotation system introduced in Ruby 3 - production-ready? Find out as we share pros and cons, how to use it with plain Ruby or with Ruby on Rails, a comparison with Sorbet, and our recommendation.
Ruby is a dynamic language and you probably already know that it doesn’t have explicit typing included in its toolset. RBS aims to introduce new tools to help with that.
RBS was introduced in Ruby 3.0.0 as the proposed solution by the Ruby Core team for explicitly typing your Ruby codebase. This is in the form of a separate file with type definitions that your IDE or other tools can use to analyze your code.
Previously, a similar tool called Sorbet was built by a team at Stripe. Sorbet helps solve type checking, but with a different approach than RBS.
You may be asking yourself if RBS is production-ready, which gems are useful for RBS, or even how to integrate it with your existing project. In this article we’ll cover the following:
The introduction of RBS in Ruby is an interesting step forward. There are developers who are for and against it. In our opinion, it all comes down to what you use it for, e.g. RBS may work great for typing signatures of class initializers and methods, but not that well with certain metaprogramming tools. It’s great to improve the efficiency and confidence of development, however, it does have some early-stage bugs to iron out.
Here are some RBS pros and cons for you to consider:
Pros of Using RBS
- Type analysis for your code
- IDE integration
- Growing RBS community and tools
- Existing type definitions for most of the common tools
Cons of Using RBS
- Metaprogramming does not work very well yet
- Early stage and there may be bugs in the tooling
- There are already more mature tools like Sorbet, etc
- Type definitions are stored in different files, increasing the number of files to keep track of
How to Use RBS
In the case of plain Ruby, you just need to declare your signatures and typings in a similar format to how you declare your methods, classes, and variables. Then, just save it as an .rbs file with the same name.
Here’s a quick look at how an .rbs file looks for an example .rb file:
# .rb file
class Developer
attr_accessor :name, :years_of_experience
def initialize(name, years_of_experience)
@name = name
@years_of_experience = years_of_experience
end
def introduce_yourself
puts "Hi, my name is #{name} and I have #{years_of_experience} years of experience"
end
end
# .rbs file
class Human
attr_accessor name: String
attr_accessor years_of_experience: Integer
def initialize: (String name, Integer years_of_experience) -> void
def introduce_yourself: -> nil
end
Rails is a little bit trickier.
Since most methods for ActiveRecord and similar tools provided by Rails use metaprogramming, RBS is not capable of identifying signatures automatically.
That’s the reason why the rbs_rails gem was created. We are going to talk more about it in one of the following sections.
4 Useful Gems for RBS
RBS is built as a standard and a gem, but the idea of the gem itself is not to be the only tool you need, but a core component for parsing these files, so third-party tools can build on top of RBS.
At Whitespectre, we investigated type-checking for one of our client’s Ruby projects, which contains many different files, classes, and entities that interact with each other. We aimed to improve the developer experience and reduce any potential bugs that could be introduced by unexpected types.
We found that the following sets of gems were useful during our investigation of RBS:
RBS gem
RBS is the main gem of this topic, and it’s already bundled with Ruby 3.0.0+ by default. RBS intends to be mostly the gem that can read RBS syntax and parse them into more semantic definitions that another tool (or even a human) can read.
This comes with a couple of very handy commands. One of them, rbs collection
,
lets you install third-party library definitions using the set of type definitions at
rbs_collection repository.
Rbs collection
will be great for handling type definitions for third-party
components like gems, especially the ones that are very common and are already posted in
their gem_rbs_collection repo.
Check out the RBS page on Github for more information on RBS gem: https://github.com/ruby/rbs Typeprof If you have a big project, you may not be sure where to start, or even feel overwhelmed by the number of changes using RBS will require.
Typeprof is a great tool to help you get started, by automatically converting your existing .rb files to .rbs.
This is a relatively small tool that can parse your .rb, analyze them, and create the corresponding .rbs file for your typing.
Just install and run…
bundle exec typeprof your_file.rb
… And it’ll create the .rbs file for you!
Please remember that this isn’t magic. So it has some limitations, especially when we are talking about metaprogramming.
The previous RBS example we shared in an earlier section, was created using Typeprof.
# .rbs file
class Human
attr_accessor name: String
attr_accessor years_of_experience: Integer
def initialize: (String name, Integer years_of_experience) -> void
def introduce_yourself: -> nil
end
The creators expect gem parsing to evolve along with the .rbs format, so it’s very likely that we’ll see improvements in the future.
You can read more about TypeProf on their Github page: https://github.com/ruby/typeprof
Rbs_rails gem
This gem will help you have access to type definitions for your ActiveRecord models and Route helpers.
In order to use it, you need to execute the following commands to let rbs_rails declare your rails typings:
bin/rails g rbs_rails:install
bin/rails rbs_rails:all
bundle exec rbs collection install
After that, you’ll have access to type definitions on your models and routes! Just remember to re-run this after you make changes to your schema.
In case you want to dig deeper into RBS Rails gem, you can go to their project page: https://github.com/pocke/rbs_rails
Steep
We solved translating your own code to type definitions, and how to use it on third-party libraries. But that’s not very useful unless you’ve a type checker to validate your code against the type definitions in your project. Steep is here to help validate code against type definitions.
This gem will provide you with a CLI tool to verify the correctness of your code against the definitions in your .rbs files.
This brings up an interesting topic, which is Editor/IDE integration. In case you use VS Code, you can use this extension. There are integrations for other editors/IDE’s like Rubymine, which according to some of our team members who use Rubymine, works smoother than VS Code.
Sadly, the VS Code extension is in a very early stage and you may find different issues along the way, from crashes to times when you need to force-restart the extension so it parses your changes in an .rbs file.
Steep’s official repository contains more information and documentation. You can check it out here: https://github.com/soutaro/steep
What’s the Difference Between Sorbet and RBS?
Sorbet is a static type checking tool created by Stripe before RBS was even a draft. It fulfills a similar purpose. Here’s our previous RBS example adapted to use Sorbet instead of RBS:
# typed: strict
class Developer
extend T::Sig
sig {returns(String)}
attr_accessor :name
sig {returns(Integer)}
attr_accessor :years_of_experience
sig {params(name: String, years_of_experience: Integer).void}
def initialize(name, years_of_experience)
@name = T.let(name, String)
@years_of_experience = T.let(years_of_experience, Integer)
end
sig {returns(NilClass)}
def introduce_yourself
puts "Hi, my name is #{name} and I have #{years_of_experience} years of experience"
end
end
The main difference you’ll find here is that the code itself and the type definitions coexist in the same file. Also, there’s more support for it since this tool has been around longer.
Sorbet was built to not only deal with the specific challenges that Stripe had but also to cover many of the scenarios that RBS does now. The Ruby team describes RBS’ main goal to be a language to define types. It’s not intended to cover things like type-checking by default. Instead, Sorbet attempts to cover both the definition and the checking.
Does this mean we should go ahead and use Sorbet instead of RBS?
Or will Sorbet get deprecated in favor of RBS?
As with almost everything in our field, it depends.
Sorbet has the advantage of having an inline syntax that works with the same ruby files you already have, but RBS, with a growing community, is the official tool for typing. Thankfully there seems to be a good synergy between both tools and they agree on using each other’s discoveries to make their tools even better.
Final Thoughts on RBS
After all the testing we did at Whitespectre, we found out that RBS is a very interesting tool, but it’s not very mature yet. Some tools have bugs, or weird behaviors that may be challenging, even more so when you’re working with a big team and not everyone has the same setup, editor, and so on.
We think that it’s a great step towards type safety and very useful type suggestions in Ruby, but it may not be the right time to integrate in a production codebase.
That said, the RBS community is growing and seems promising. We believe that in the not-so-distant future it’ll be a great addition to production-grade applications, but for now, it’s better to just keep an eye on it and look for the right time to adopt this technology.