Sorbet vs RBS in Ruby

Altimetrik Poland Tech Blog
5 min readApr 18, 2023

--

Source: https://sorbet.org/blog/2022/07/27/srb-tapioca

Introduction

Usually when we define programming languages, quite often we can read about the fact that they are divided into statically typed or dynamically typed languages.

This also applies to the Ruby language, which is said to be dynamically typed, but also strongly typed. But what exactly does this mean? It is the definition of how variables behave and interact when they are first declared. If you’ve ever faced languages like C, for example, or Java you might have seen declarations as follows:

int variable_name_that_is_a_number 

This means that we are declaring a variable that will only accept numeric values. This can be seen later when we try to assign a string to the variable, which in turn will cause an error.

In the case of Ruby, we do not declare what type our variable will be, and we could later try to assign variables of other types to the same one, therefore.

variable = 2 

variable = “2”

It will not cause an error, but will simply change the value of the variables to “2”.

This, of course, can be helpful in many situations, but it can also lead to problematic situations if there is something that was not considered in the beginning.

As always in this case, there is a solution that helps Ruby behave as if it is statically typed, or probably rather gradually typed. While the language itself doesn’t change, there are several static type checkers that help us define what should be used and where. While this doesn’t change Ruby to a statically typed language, it certainly helps.

One such tool is known as Sorbet. It’s a gem introduced to both normalize how methods are supposed to behave, but also so that any errors resulting from the wrong type of variables used in functions will bring us better errors.

How does it work?

Generally, Sorbet uses signatures, which show what types of variables in methods should be. They are used like this:

sig {params(x: SomeType, y: SomeOtherType).returns(MyReturnType)} 
def foo(x, y); ...; end

While signatures have started to become more than just comments on code, due to the development of Sorbet, they have become part of it and help enforce variable types.

Another way to do something similar would be RBS. RBS, unlike Sorbet, takes a different approach and stores function definitions in separate files with the extension .rbs in which we define a kind of “interfaces” for our classes or modules.

class User 
attr_reader login: String
attr_reader email: String

def initialize: (login: String, email: String) -> void
end

class Bot
attr_reader name: String
attr_reader email: String
attr_reader owner: User

def initialize: (name: String, owner: User) -> void
end

This makes keeping track of what should be function definitions easier and better maintained, but it also makes all changes to methods stored in two different places, which again can be a bit difficult to predict in advance.

Metaprogramming & overloading

While both tools are quite useful and help you organize your work well, Ruby’s change to statically typed method definitions seems to hinder many features of the language that most Ruby programmers like. The fact that methods can be overloaded seems to be an innate feature of Ruby.

So, of course, both methods support function overloading, although both do so using different approaches.

Sorbet uses multiple sigs that are declared one below the other.

sig {params(x: String).returns(MyReturnType)} 
sig {params(x: Integer, y: Integer).returns(MyReturnType)}
def foo(x, y = 0); ...; end

While RBS uses pipes to show the possible parameter types of both functions, as well as their return value, as below:

def foo: (x: String | Integer, y: Integer) -> MyReturnType | MyOtherReturnType 

We are still left with a lot of issues related to possible problems with dynamically declared methods, or those provided by, for example, gems.

In the case of RBS, this is quite simple, as we can simply declare them, with the only exception that if the result of the method is a singleton of the class, the out return should be.

-> singleton(MyReturnType) 

In the case of Sorbet, this is a bit more complicated, because it would actually be problematic where to declare such signatures.

Sorbet came up with .rbi files, which are additional files that are declared in a specific tree and contain signatures for just such declared methods, which then becomes much clearer where these methods should be declared, besides it is quite simple.

Here it is also worth noting the gem tapioca, which helps us create .rbi files. They work just like normal .rb files, they just don’t need method implementations which is quite similar to .rbs files.

So why use sorbet when it also creates separate .rbi files?

Well, both are different approaches to the same problem, and it’s worth noting that Sorbet can only be used as signatures that are next to method definitions. This obviously makes applications smaller, and easier to maintain. In case .rbi files also have to be used, then it mainly depends on preference and whether one of the methods was already used.

I think Sorbet is currently a better method for handling static type checking, especially since its scaffolding tools are more useful for both generating and maintaining files.

Summary/What does the future hold?

Which one should you use with an eye to maintaining your application for the long term? As always, it’s not possible to test exactly whether both ways are maintainable in the long run, but we can try to look at them:

  1. RBS: It is developed by the core Ruby team. It is a strong indication that it will stay with us for a long time and has a good chance of becoming the dominant way.
  2. Sorbet: This tool, developed by the Stripe team, is certainly in pole position in the race right now, mainly because of the number of its features and the fact that it was released a little earlier than RBS. According to the developers, they have started working with the core Ruby team.

Words by Michał Rusinek, Senior Engineer

Editing by Kinga Kuśnierz, Content Writer

https://www.linkedin.com/in/kingakusnierz/

--

--

Altimetrik Poland Tech Blog

This is a Technical Blog of Altimetrik Poland team. We focus on subjects like: Java, Data, Mobile, Blockchain and Recruitment. Waiting for your feedback!