A Rusting Rubyist II

Rust String Methods in a Ruby Module

Mike Piccolo
5 min readJul 6, 2015

This is part two of a series where I try to stumble my way through creating a Rust scraping library that will be embeddable in a Ruby module. If you are interested in starting from the beginning you can check out all my posts here: https://medium.com/@mfpiccolo

Follow along with the part-2 branch of the scrape repo.

Last time we set up part-1 which allowed us to call a embeddable rust function that summed two numbers together and returned the results from a Ruby module. That is all well and good but we need to step our game up if we are going to get to web scraping any time soon. This time around we are going to be passing in strings and manipulating them and passing them back to the Ruby module.

Strings in Ruby vs Strings in Rust

One thing that I learned right away in Rust that surprised me was how many different types there were. In Ruby we are all spoiled because for the most part a string is string. There is a bunch of ways to make a string but they all end up being an instance of the String class.

I never realized how nice it was to not have to worry about type conversion until I tried out Rust. In Rust a string can be represented in a whole bunch of different ways.

The types we need to know about for this project are:

std::string, std::str, std::ffi::CString, std::ffi::CStr, libc::types::os::arch::c95::c_char

Reverse String in Ruby

In Ruby you could do something like this.

Reverse String in Rust

Lets set up what our function is going to look like and a test.

Okay. Now lets try and figure out what we we need to make this happen. Digging through the docs it looks like what we want is the rev() method. The only thing is that this method is implemented on a the Chars type and we have a String!

Well luckily we can convert the chars() string method. Now lets add the code:

And run the test.

Uh oh! Error:

$ cargo test
src/lib.rs:7:19: 7:41 error: mismatched types:
expected `collections::string::String`,
found `&’static str`
(expected struct `collections::string::String`,
found &-ptr) [E0308]
src/lib.rs:7 assert!(reverse(“Don’t use palindrome”) == “emordnilap esu t’noD”);
^~~~~~~~~~~~~~~~~~~~~~

This is telling us that the string that we are passing the function in our test is not what are expecting. You can read about what is going on here if you would like but for now lets just call to_string() on it.

Okay. Run test again.

$ cargo test
src/lib.rs:2:3: 2:18 error: mismatched types:
expected `collections::string::String`,
found `core::iter::Rev<core::str::Chars<’_>>`
(expected struct `collections::string::String`,
found struct `core::iter::Rev`) [E0308]
src/lib.rs:2 s.chars().rev()
^~~~~~~~~~~~~~~

RRR!! The compiler hates you. Apparently you are trying to return a `core::iter::Rev<core::str::Chars<’_>>` when you should be returning a string. Back to the docs to find out how to convert that back into a string… Okay, it looks like Rev implements the collect() method. that sounds promising.

Run the test again and yes!

$ cargo test
test it_works … ok

We have ourselves a working reverse method.

Reverse Method Embedded in Ruby

First off we need to figure out how to pass in a Ruby string to rust. I’ll save you the pain of writing out the debugging process here since it took me a long time to figure out what I am assuming is the right way to do this. I tried to use CString and CStr which worked in some cases but segfaulted when trying to pass them back to Ruby.

When working with FFI, *const libc::c_char is the type that we want to be using when receiving strings and returning them to Ruby. Lets set up a function that ffi can use. Notice we need an external crate `libc`.

We now need to convert our c_char into something we can work with in Rust. To achieve this, we are going to have to do something “unsafe”. Scary, I know. I not one hundred percent on this aspect so if you are a Rust dev and disagree, please comment and let us all know a better way.

To break this down, line 3 is creating a reference to a CStr from a pointer to a c_char that we passed in. Then in line 4 we are converting that into a reference to a Rust str. Then we can use the code from the rust function we wrote earlier on line 5. Remember to declare the type in the let statement.

All that is left to do is return the proper type so ffi can use it in the Ruby module.

Woah. That was a lot of converting. Not quite as elegant as our Ruby one liner. Hopefully this will all pay of in the form of power and speed later on down the road!

A few things to note here are we have to enable some features otherwise the compiler with yell at us. Also do not forget the `#[no_mangle]` (like I just did) and spend way to long trying to figure out why ffi cannot find your function.

error: use of unstable library feature ‘cstr_memory’: recently added

Now run the scrape Ruby file.

$ ruby scrape.rb
Gone Dun It!

Hooray! We did it. String manipulation from Ruby to Rust and back to Ruby.

Refactoring and Concat

Now that we know how to do it, it is easy to write other functions like concat. But before we do that lets do some refactoring because we are going to be doing that same converting in both functions. (Remember these, they may come in handy)

These are simply extracting out the converting steps we used above into functions. The only thing to note here is that the ruby_string_to_ref_str function needs to specify the lifetime. I am not going to get into (1) because it is a big topic and (2) I don’t fully understand it yet so it would be better to just read the docs.

With these functions now we can easily write concise external string manipulation functions.

Run the Ruby file.

$ ruby scrape.rb
Gone Dun It!
Gone Dun It!

Next Time

Well that went pretty well! I think next time we will start pulling in some external libraries so we can do some awesome stuff like http requests. Time to go major web. This is getting fun right?!?

Part III

Special Thanks

The rust community, for the most part, is pretty nice to newbs so don’t be afraid to ask a Stack Overflow question or get on the rust IRC channel. Special thanks to Stack Overflow users Adrian, shepmaster, Chris Morgan and DK. Also Steve Klabnik for doing a great job on the docs.

And of course don’t hesitate to hit me up in the comments or on twitter@mfpiccolo

--

--

Mike Piccolo

JavaScript/Ruby, software developer, business consultant, entrepreneur, outdoorsman. https://fullstacklabs.co/