An Exploration of RBS by Ruby: Is it Production-Ready?

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.

Let’s Chat