The most common criticism of Ruby is its performance when compared to other languages. However, Stripe, one of the world's largest and most influential Ruby shops, is working hard to resolve this issue.
Along with their existing Sorbet Ruby Gem, which provides type checking, they're developing an ahead-of-time Ruby compiler with an emphasis on performance. This article will explore both type checking and the new compiler.
What Is Sorbet?
Sorbet is a Ruby project developed by Stripe that aims to improve developer productivity and the performance of Ruby code. What started as a gem to incrementally introduce type checking into Ruby projects has grown into an experimental ahead-of-time compiler for Ruby.
The Ruby Type Checking Gem
The Sorbet Ruby gem allows developers to turn Ruby, a dynamically typed language, into a statically typed language.
Programming languages are either statically or dynamically typed. In a dynamically typed language, the interpreter infers each object’s type at runtime. In statically typed languages, programmers explicitly state the type of an object when writing the code, typically when a variable is declared.
The Sorbet Ruby gem provides a way to incrementally adopt dynamic typing into Ruby code. Picking and choosing where static typing is most useful keeps some of the Ruby magic while improving error rates and even developer productivity.
Compare to Types in Ruby 3
Ruby 3 introduced RBS to the Ruby community. It includes a syntax for introducing Ruby types directly into the language, with no gem necessary. If you haven’t adopted Ruby 3, you can still take advantage of RBS with the gem, which is named rbs
.
One of the major differences between Sorbet and RBS and Sorbet is that Sorbet requires type annotations to be added directly in your code, while RBS requires the creation of new files that lay out the structure of your Ruby code. These RBS files contain class and method signatures and are then used by type checkers (like Sorbet) to type check the code.
Why This Is Useful
Dynamic typing is one of the features that makes Ruby such a fun language. Because there is no need to indicate types, code ends up being cleaner and easier to read, although a little less clear. In practice, programmers often make mistakes when assuming an object's type, leading to errors that are not apparent until runtime, where they cause problems for users.
My favorite way to describe Ruby is "reckless". The language doesn't care if you know what you're doing. It will let you change the implicit type of a variable without knowing. The following Ruby snippet is completely valid:
some_word = "This is a string"
some_word = 27
some_word = 2.7
This code can result in type errors if you later attempt to perform a math operation on a string or even use a string method on an integer. Static type checking can mitigate both the frequency and the impact of these sorts of errors by throwing warnings as part of the check process, which can easily be run in a continuous integration workflow.
Impact on Stripe's Error Rate
By adding type annotations to Ruby code and using Sorbet to check the codebase, Stripe has reduced the number of errors in their codebase and improved the reliability of their services. One of the greatest benefits of using Sorbet is its ability to identify type errors early in the development process, before code is deployed.
Developers can catch and fix errors before they have a chance to cause issues in production. Uninitialized Constant errors and NoMethod errors, which every Ruby developer has certainly encountered, are caught in the type checking process instead of in production.
Sorbet also protects against regression by flagging changes that may be incompatible with the existing type annotations. Overall, the use of Sorbet has helped Stripe to improve the quality and reliability of their code, leading to a reduction in errors and an overall improvement in their system's reliability. They talk more about it in their recent blog post.
Using the Sorbet Gem in a Rails Project
Before using Sorbet to type check, three gems need to be installed.
Add the following code to your Gemfile:
gem 'sorbet'
gem 'sorbet-runtime'
gem 'tapioca'
Next, install the gems:
bundle install
Then, use tapioca to set up interface files:
bundle exec tapioca init
Next, generate .rbi files for sorbet to run type checking in a Rails project. Run the following:
bin/tapioca gems
Then, add the following:
bin/tapioca require
Finally, run the following:
bin/tapioca dsl
Now, you can run Sorbet type checking with the following:
srb tc
Sorbet silences type checking warnings at the start. The Sorbet Static Checks documentation notes that to enable type checking in a file, you'll need to insert a "magic comment" at the top of the file:
# typed: true
at the top of a file will instruct Sorbet to raise errors for non-existent methods or argument mismatching.
# typed: strict
will enforce that all methods have signatures.
This is enough to get started with type checking, but the Sorbet docs provide more details on setting up type checking with Sorbet. If you are using tapioca, the tapioca gem's readme has more details.
The Sorbet Ruby Compiler
In addition to being a static type checker, Sorbet is also an experimental Ruby compiler. The Sorbet compiler is a separate component of the Sorbet toolchain that is designed to take Ruby source code and compile it to be faster.
The Sorbet compiler is still in the experimental stage and is not yet in production environments outside of Stripe. Stripe does claim, however, that they've seen up to a 170% speed increase since adopting it for their APIs.
The Sorbet compiler works by analyzing Ruby source code and performing a series of optimization passes on it. These optimization passes can include things like inlining method calls, dead code elimination, and other techniques designed to make the code run faster.
One of the most notable techniques that Sorbet uses for performance improvements is ahead-of-time compiling.
What is "Ahead-of-Time"?
An ahead-of-time (AOT) compiler compiles source code into machine code before the code is executed. AOT compilers are commonly used with compiled languages, such as C, C++, and Rust, but can also be used with interpreted languages, such as Ruby. In these cases, the AOT compiler takes the source code and produces machine code that can be run on the target platform.
AOT compilers are a useful tool for optimizing the performance and portability of code and can be an important part of a developer's toolkit. Ahead-of-time compilation takes advantage of type information present in a statically typed language, which Sorbet provides for Ruby. A just-in-time (JIT) compiler notes types at runtime; Sorbet provides this information for Ruby code, and the Sorbet compiler leverages it.
Reducing API Latency by Spending Less Time in Compute
API latency is caused by a combination of things, including unavoidable network travel time, I/O operations, and Compute. The Sorbet Ruby compiler improves API latency by reducing the amount of time the language spends in compute. It makes a lot of sense that speeding up the language itself would lead to faster API endpoints, but it's not an easy task.
To achieve this, the Sorbet compiler uses a variety of optimization techniques to run code faster. It's worth noting that reducing API latency does not have a one-size-fits-all solution. Different APIs have different performance requirements and may necessitate different approaches to optimization. However, using a tool like the Sorbet compiler can be a powerful way to improve the performance of your Ruby-based APIs and deliver a faster, more reliable experience to your users.
Conclusion
In conclusion, the Sorbet Ruby compiler, when combined with Sorbet type checking, is a powerful tool for improving the performance and reliability of Ruby applications. With static type checking, Sorbet helps developers catch type errors before code is run, leading to fewer errors and a better user experience. Additionally, the Sorbet compiler can significantly reduce API latency by optimizing code for faster execution.
Although it is still in the experimental stage, the Sorbet compiler has the potential to become a valuable tool for developers working with Ruby. Overall, Sorbet is an exciting development in the Ruby ecosystem and is worth exploring if you’re looking to improve the performance and reliability of your Ruby-based applications.
If you're looking to learn more about the Sorbet compiler, Stripe's announcement is a great place to start. They point out that all the code for the compiler lives in the Sorbet repository on GitHub, which contains instructions for running the compiler, testing the compiler, and contributing.