Skip to content

Latest commit

 

History

History
114 lines (76 loc) · 3.9 KB

File metadata and controls

114 lines (76 loc) · 3.9 KB
theme _class paginate marp backgroundColor
default
lead
true
true

bg left:40% 80%

Ruby on Rust

An intro to writing native Ruby extensions in Rust.

You may be new to Rust, and that's OK! You'll find that Rust has many of the things you love about Ruby.


About Me

👋 I'm @ianks, and I work on the liquid-perf team. We are using Rust + WASM to improve the performance of Shopify's Liquid templating engine.

  • Live in Atlanta, GA
  • Creator of the oxidize-rb open-source org
  • Maintainer of rb-sys
  • Added Rust support to Bundler / RubyGems

What is a Ruby Extension?

For a native gem, we bypass this mechanism entirely and instead exposes native machine code to Ruby. In our native code, we can use the Ruby C API to interact with the Ruby VM.

#include "hello.h"

VALUE hello(VALUE self) {
  return rb_str_new_cstr("hello");
}

void Init_hello(void) {
  rb_define_global_function("hello", hello, 0);
}

Why does it work with Rust and not other languages?

  • Speaking in C, "lingua franca"
  • Can compile functions with the C calling conventions
  • Align items in memory in a way that C understands.
  • Due to Rust's robust C FFI, you can code anything in Rust that you could with C.

What makes Rust a good choice for Ruby extensions?

  • Speed: Rust is fast, comparable to C.
  • Memory Safety: Rust is designed to prevent memory errors.
  • No GC: Means we don't have to worry about 2 GCs running at the same time.
  • Ecosystem: Rust has a large ecosystem of libraries and tools (cargo ~= bundler).
  • Familiarity: Rust has many features that Ruby developers will be familiar with.

Use Cases

  • Performance: You have identified a performance bottleneck that can't be solved in Ruby.
  • Complexity: You have a complex native library in C that would benefit from using Rust enums, structs, and traits (yjit).
  • Bindings to Rust: You want to make use of a Rust crate (wasmtime, cssparser, etc).
  • Bindings to C: You want to make use of a C library (libxml, libcurl, etc), but want memory safety. You can use a Rust crate to wrap the C library (LLVM/inkwell, geos-rs, etc).

Learning Curve

The rumours are true, Rust has a steep learning curve. You will battle with the borrow checker, and fight with the compiler as you learn the language.

  • Rust errors typically happen at compile time (rather than segfaulting at runtime)
  • Can be productive in Rust without fully groking the borrow checker
  • Community is very helpful, with a general sentiment of "we've all been there" towards beginners
  • You can do it!

Shipping a Ruby Extension

  • Launching the liquid-wasm production verifier
  • Things just worked
  • Rust gives you a "confidence to ship" that you don't get with C/C++.

Happy Path, or "How to write a Ruby extension in Rust"

  1. magnus for to handle Ruby C API bindings.

    • Drop into rb-sys for low-level Ruby APIs.
  2. cargo for dependency management.

  3. Use the rb-sys gem for to make cargo work with Ruby (via create_rust_makefile).

  4. rake-compiler for compiling the extensions (as you would with C)

  5. Not so distant future: Support for cargo in RubyGems (done, available in Ruby 3.2).

  6. Not so distant future: Support for bundle gem --rust in Bundler (PR open, likely Ruby 3.2).


Demo Time

  • demos/ext/c
  • demos/ext/rust_rbsys
  • demos/ext/rust_magnus
  • BONUS: Add support for contains? in demos/ext/rust_magnus_geo for a 🦄