Go to file
Godfrey Chan f4dcbf6ea1 v0.7.2 2017-10-09 22:53:01 -07:00
crates v0.7.2 2017-10-09 22:53:01 -07:00
examples v0.7.2 2017-10-09 22:53:01 -07:00
ruby v0.7.2 2017-10-09 22:53:01 -07:00
scripts Auto release tagged builds on Travis 2017-04-26 09:23:13 -07:00
src Merge pull request #128 from tildeio/consume-self 2017-10-09 22:18:30 -07:00
.appveyor.yml Add json_builder to CI 2017-10-09 22:40:47 -07:00
.gitignore Ignore .vagrant 2017-03-09 10:03:57 -08:00
.travis.yml Add json_builder to CI 2017-10-09 22:40:47 -07:00
CHANGELOG.md v0.7.2 2017-10-09 22:53:01 -07:00
Cargo.toml v0.7.2 2017-10-09 22:53:01 -07:00
LICENSE Add LICENSE 2017-02-16 14:42:06 -08:00
README.md Add compatibility section 2017-05-18 08:50:37 -07:00
RELEASE.md Fix numbering in RELEASE.md 2017-04-24 10:09:48 -07:00
Rakefile Add json_builder to CI 2017-10-09 22:40:47 -07:00
Vagrantfile Add scripts dir, renable Vagrant provisioning 2017-03-03 12:21:57 -08:00
console.d.ts Initial commit 2016-05-06 15:01:20 -05:00

README.md

Travis Build Status AppVeyor Build Status

Helix

Helix allows you to write Ruby classes in Rust without having to write the glue code yourself.

ruby! {
    class Console {
        def log(string: String) {
            println!("LOG: {}", string);
        }
    }
}
$ rake build
$ bundle exec irb
>> require "console"
>> Console.log("I'm in your Rust")
LOG: I'm in your Rust
 => nil

Why Helix?

Read the Introducing Helix blog post for a quick introduction to the project!

Getting Started

https://usehelix.com/getting_started

Demos

https://usehelix.com/demos

Roadmap

https://usehelix.com/roadmap

Compatibility

Helix has been tested with the following, though other combinations may also work.

  • cargo 0.18.0 (fe7b0cdcf 2017-04-24)
  • rustc 1.17.0 (56124baa9 2017-04-24)
  • ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-darwin16]
  • Bundler version 1.14.6

Contributing

If you'd like to experiment with Helix, you can start with some of the examples in this repository.

Clone and download the Helix repository:

$ git clone https://github.com/tildeio/helix
$ cd helix

Navigate to the console example folder and bundle your Gemfile:

$ cd examples/console
$ bundle install

Run rake irb to build and start irb:

$ bundle exec rake irb

Try running some of the methods defined in examples/console/src/lib.rs:

> c = Console.new
Console { helix: VALUE(0x7fdacc19a6a0) }
 =>
> c.hello
hello
 => nil
> c.loglog('hello', 'world')
hello world
 => nil

Coercions

When you define a method in Helix using def, you can specify any Rust type in its type signature.

Under the hood, Helix will automatically coerce the Ruby type to the specified Rust type, doing appropriate type checks before passing the values into Rust.

ruby! {
    class Console {
        def log(&self, string: &str) {
            println!("LOG: {}", string);
        }
    }
}
$ irb
>> require "console"
>> Console.new.log({})
TypeError: No implicit coercion of {} into String
	from (irb):2:in `log'
	from (irb):2
	from /Users/ykatz/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'

The Helix Coercion Protocol

Under the hood, Helix does not hardcode all possible coercions from Ruby into Rust. Instead, it defines a two-part protocol that any crate can implement to define coercions from Ruby values into their types.

pub trait UncheckedValue<T> {
    fn to_checked(self) -> CheckResult<T>;
}

pub trait ToRust<T> {
    fn to_rust(self) -> T;
}

Implementations of these traits use these concrete types:

pub type CheckResult<T> = Result<CheckedValue<T>, String /* error */>;

pub struct CheckedValue<T> {
    pub inner: VALUE;
    // other private fields
}

impl<T> CheckedValue<T> {
    // instantiating a CheckedValue<T> is an assertion that the follow-up
    // call to `to_rust` is safe.
    pub unsafe fn new(inner: VALUE) -> CheckedValue<T>;
}

For reference, here is the implementation of the coercion from a Ruby String to Rust String.

impl UncheckedValue<String> for VALUE {
    fn to_checked(self) -> CheckResult<String> {
        // check whether the VALUE is actually a String
        if unsafe { sys::RB_TYPE_P(self, sys::T_STRING) } {
            // assert that we can guarantee that to_rust() can return a Rust String safely
            Ok(unsafe { CheckedValue::<String>::new(self) })
        } else {
            let val = unsafe { CheckedValue::<String>::new(sys::rb_inspect(self)) };
            Err(format!("No implicit conversion of {} into String", val.to_rust()))
        }
    }
}

impl ToRust<String> for CheckedValue<String> {
    fn to_rust(self) -> String {
        // we're sure that these calls are safe, because we already went through the type
        // checking protocol in VALUE.to_checked().
        let size = unsafe { sys::RSTRING_LEN(self.inner) };
        let ptr = unsafe { sys::RSTRING_PTR(self.inner) };
        let slice = unsafe { std::slice::from_raw_parts(ptr as *const u8, size as usize) };
        unsafe { std::str::from_utf8_unchecked(slice) }.to_string()
    }
}

This protocol allows us to fully type check a method's arguments before starting any of the coercions. It happens automatically based on the type signature you use in your Rust method def.