Reimagining Talent as Infrastructure: Building the AI-First Enterprise
AI-powered talent ecosystems are redefining enterprise success driving faster hiring, agile workforce mobility, ethical AI governance, and measurable growth.
In discussions about programming languages, the classification into statically typed or dynamically typed languages is common. This categorization also applies to Ruby, often described as dynamically typed yet strongly typed. But how do Sorbet vs RBS in Ruby impact this distinction? It’s crucial to understand how variables behave and interact upon declaration.
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.
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.
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.
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:
Ruby is a dynamically typed and strongly typed language. You do not need to declare types when assigning variables, and you can reassign them to different types later. However, Ruby enforces type safety at runtime by preventing the use of incompatible types together.
Gradual typing allows developers to introduce type annotations only where needed. You can type critical sections of your code while keeping the rest as plain Ruby. This provides a balance between flexibility and early error detection without requiring a complete rewrite.
Sorbet is a type checker that supports inline type signatures and optional runtime checks. It uses .rbi files to define method signatures introduced by gems or metaprogramming.
RBS is Ruby’s official type signature format, stored in .rbs files. It works with tools like Steep or TypeProf to provide static analysis.
Which should you choose?
Go with Sorbet if you prefer inline annotations and want access to Sorbet’s rich tooling ecosystem. Choose RBS if you like keeping type definitions separate from code and want compatibility with Ruby’s native type tooling.
.rbs files contain Ruby’s standard type signatures that describe classes and methods outside your application code.
.rbi files are specific to Sorbet. They define interfaces for dynamically generated methods or those coming from gems.
These files help static analysis tools understand code that Ruby generates at runtime.
Start small by adding types to a high-risk class or a key code path. Gradually expand type coverage, integrate checks into your CI pipeline, and use tools like Tapioca to generate interfaces for gems. View type annotations as documentation that can be validated automatically.
Use .rbi files in Sorbet or .rbs files in RBS to describe dynamic code. Tools like Tapioca (for Sorbet) or rbs prototype (for RBS) can scan your app and generate initial type definitions. These should be version-controlled and updated as your code evolves.
Static type checks are performed during development or in CI and do not impact runtime performance. Sorbet’s optional runtime type checks can add some overhead if enabled, but most teams either disable them or use them sparingly in production.
No. Type annotations help catch structural and type-related issues early. However, they do not replace the need for tests that validate behavior, logic, and edge cases. The most reliable approach combines both static types and automated testing.
AI-powered talent ecosystems are redefining enterprise success driving faster hiring, agile workforce mobility, ethical AI governance, and measurable growth.
Embedded finance isn’t merely a product evolution, it’s a structural shift in how financial services are consumed, delivered, and monetized. For banks, embedded finance must be treated as a strategic opportunity to lead ecosystem value creation and not a defensive response to fintech disruption.
Generative AI is transforming supply chains by reducing decision latency, enabling real-time scenario planning, and turning supply chain intelligence into a strategic business enabler. Discover how GenAI reshapes planning, resilience, and growth.
Altimetrik is committed to protecting your personal information. To apply for a position, you will need to provide your email address and create a login. Your information will be used in accordance with applicable data privacy laws, our Privacy Policy, and our Privacy Notice.
