Skip to main content

Cookbook

This page contains ready-to-use recipes for common patterns when building Ruby extensions with Rust.

String Manipulation

Fast String Reversal with Unicode Support

This example uses the unicode-segmentation crate to correctly reverse a string containing multi-byte grapheme clusters.

use magnus::{function, prelude::*, Error, Ruby};
use unicode_segmentation::UnicodeSegmentation;

fn reverse_preserving_graphemes(input: String) -> String {
input.graphemes(true).rev().collect()
}

#[magnus::init]
fn init(ruby: &Ruby) -> Result<(), Error> {
let module = ruby.define_module("StringUtils")?;
module.define_singleton_method("reverse_unicode", function!(reverse_preserving_graphemes, 1))?;
Ok(())
}

Data Processing

Parallel Array Processing

This recipe demonstrates how to release the GVL to process a Ruby array in parallel using the rayon crate.

use magnus::{RArray, Error, TryConvert};
use rayon::prelude::*;
use rb_sys::rb_thread_call_without_gvl;
use std::os::raw::c_void;

struct ProcessData {
items: Vec<i64>,
result: Vec<i64>,
}

extern "C" fn process_without_gvl(data: *mut c_void) -> *mut c_void {
unsafe {
let data = &mut *(data as *mut ProcessData);
data.result = data.items.par_iter().map(|&x| x * x).collect();
}
std::ptr::null_mut()
}

fn parallel_map_squares(array: RArray) -> Result<RArray, Error> {
let items: Vec<i64> = array.to_vec()?;
let mut data = ProcessData { items, result: Vec::new() };

unsafe {
rb_thread_call_without_gvl(
Some(process_without_gvl),
&mut data as *mut _ as *mut c_void,
None,
std::ptr::null_mut(),
);
}

Ok(RArray::from_vec(data.result))
}

CSV Processing

This example uses the csv crate to parse a CSV string into an array of Ruby hashes.

use magnus::{RArray, RHash, Symbol, Error, exception, Ruby};

fn parse_csv_to_hashes(ruby: &Ruby, csv_string: String) -> Result<RArray, Error> {
let mut reader = csv::Reader::from_reader(csv_string.as_bytes());
let headers = reader.headers().map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?.clone();
let result = RArray::new();

for record in reader.records() {
let record = record.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
let hash = RHash::new();
for (header, value) in headers.iter().zip(record.iter()) {
hash.aset(Symbol::new(header), value)?;
}
result.push(hash)?;
}

Ok(result)
}

JSON Handling

This recipe uses serde_json to parse a JSON string into Ruby objects, providing a faster alternative to Ruby's default JSON library.

use magnus::{Value, Error, Ruby};

fn parse_json(ruby: &Ruby, json_string: String) -> Result<Value, Error> {
let value: serde_json::Value = serde_json::from_str(&json_string)
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
serde_magnus::serialize(ruby, &value).map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))
}

File Operations

This recipe efficiently processes a large file line-by-line without loading the entire file into memory.

use magnus::{Error, Ruby};
use std::fs::File;
use std::io::{BufRead, BufReader};

fn count_lines_with_pattern(ruby: &Ruby, path: String, pattern: String) -> Result<i64, Error> {
let file = File::open(&path)
.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?;
let reader = BufReader::new(file);
let mut count = 0;

for line in reader.lines() {
if line.map_err(|e| Error::new(ruby.exception_runtime_error(), e.to_string()))?.contains(&pattern) {
count += 1;
}
}

Ok(count)
}

Cryptography

This example uses the sha2 crate to compute a SHA256 hexdigest of a string.

use magnus::{RString, Error};
use sha2::{Sha256, Digest};

fn sha256_hexdigest(input: RString) -> Result<String, Error> {
let data = unsafe { input.as_slice() };
let mut hasher = Sha256::new();
hasher.update(data);
let result = hasher.finalize();
Ok(hex::encode(result))
}