{"id":13437961,"url":"https://github.com/d-unsed/ruru","last_synced_at":"2025-04-04T09:08:29.715Z","repository":{"id":53503359,"uuid":"47496133","full_name":"d-unsed/ruru","owner":"d-unsed","description":"Native Ruby extensions written in Rust","archived":false,"fork":false,"pushed_at":"2021-03-27T17:31:47.000Z","size":5765,"stargazers_count":827,"open_issues_count":38,"forks_count":40,"subscribers_count":28,"default_branch":"master","last_synced_at":"2024-05-22T03:12:47.363Z","etag":null,"topics":["mri","native-ruby-extensions","ruby","ruru","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d-unsed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-12-06T12:38:18.000Z","updated_at":"2024-03-31T23:10:01.000Z","dependencies_parsed_at":"2022-09-10T14:52:48.691Z","dependency_job_id":null,"html_url":"https://github.com/d-unsed/ruru","commit_stats":null,"previous_names":["d-unsed/ruru","d-unseductable/ruru"],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-unsed%2Fruru","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-unsed%2Fruru/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-unsed%2Fruru/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-unsed%2Fruru/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d-unsed","download_url":"https://codeload.github.com/d-unsed/ruru/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247149501,"owners_count":20891954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mri","native-ruby-extensions","ruby","ruru","rust"],"created_at":"2024-07-31T03:01:01.716Z","updated_at":"2025-04-04T09:08:29.696Z","avatar_url":"https://github.com/d-unsed.png","language":"Rust","readme":"# Ruru (Rust + Ruby)\n\n## Native Ruby extensions in Rust\n\n[![](http://meritbadge.herokuapp.com/ruru)](https://crates.io/crates/ruru)\n[![Documentation](https://docs.rs/ruru/badge.svg)](https://docs.rs/ruru)\n[![Build Status](https://travis-ci.org/d-unseductable/ruru.svg?branch=master)](https://travis-ci.org/d-unseductable/ruru)\n[![Build status](https://ci.appveyor.com/api/projects/status/2epyqhooimdu6u5l?svg=true)](https://ci.appveyor.com/project/d-unseductable/ruru)\n[![Gitter](https://badges.gitter.im/rust-ruru/general.svg)](https://gitter.im/rust-ruru/general?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n[![Code Triagers Badge](https://www.codetriage.com/d-unseductable/ruru/badges/users.svg)](https://www.codetriage.com/d-unseductable/ruru)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"http://this-week-in-ruru.org/assets/images/logo.png\" width=\"350\" height=\"350\"\u003e\n  \u003cbr\u003e\n  \u003cb\u003e\u003ca href=\"https://docs.rs/ruru\"\u003eDocumentation\u003c/a\u003e\u003c/b\u003e\n  \u003cbr\u003e\n  \u003cb\u003e\u003ca href=\"http://this-week-in-ruru.org\"\u003eWebsite\u003c/a\u003e\u003c/b\u003e\n  \u003cbr\u003e\n\u003c/p\u003e\n\nHave you ever considered rewriting some parts of your ~~slow~~ Ruby application?\n\nJust replace your Ruby application with Rust, method by method, class by class. It does not require you\nto change the interface of your classes or to change any other Ruby code.\n\nAs simple as Ruby, as efficient as Rust.\n\n## Contents\n\n* [Examples](#examples)\n  - [The famous `String#blank?` method](#the-famous-stringblank-method)\n  - [Simple Sidekiq-compatible server](#simple-sidekiq-compatible-server)\n  - [Safe conversions](#safe-conversions)\n  - [Wrapping Rust data to Ruby objects](#wrapping-rust-data-to-ruby-objects)\n  - [True parallelism](#true-parallelism)\n  - [Defining a new class](#defining-a-new-class)\n  - [Replacing only several methods instead of the whole class](#replacing-only-several-methods-instead-of-the-whole-class)\n  - [Class definition DSL](#class-definition-dsl)\n  - [Calling Ruby code from Rust](#calling-ruby-code-from-rust)\n* [... and why is FFI not enough?](#-and-why-is-ffi-not-enough)\n* [How do I use it?](#how-do-i-use-it)\n* [Contributors are welcome!](#contributors-are-welcome)\n* [License](#license)\n\n## Examples\n\n### The famous `String#blank?` method\n\nThe fast `String#blank?` implementation by Yehuda Katz\n\n```rust,no_run\n#[macro_use]\nextern crate ruru;\n\nuse ruru::{Boolean, Class, Object, RString};\n\nmethods!(\n   RString,\n   itself,\n\n   fn string_is_blank() -\u003e Boolean {\n       Boolean::new(itself.to_string().chars().all(|c| c.is_whitespace()))\n   }\n);\n\n#[no_mangle]\npub extern fn initialize_string() {\n    Class::from_existing(\"String\").define(|itself| {\n        itself.def(\"blank?\", string_is_blank);\n    });\n}\n```\n\n### Simple Sidekiq-compatible server\n\n[Link to the repository](https://github.com/d-unseductable/rust_sidekiq)\n\n### Safe conversions\n\nSince 0.8.0 safe conversions are available for built-in Ruby types and for custom types.\n\nLet's imagine that we are writing an HTTP server. It should handle requests which are passed from\nRuby side.\n\nAny object which responds to `#body` method is considered as a valid request.\n\n```rust,no_run\n#[macro_use]\nextern crate ruru;\n\nuse std::error::Error;\nuse ruru::{Class, Object, RString, VerifiedObject, VM};\n\nclass!(Request);\n\nimpl VerifiedObject for Request {\n    fn is_correct_type\u003cT: Object\u003e(object: \u0026T) -\u003e bool {\n        object.respond_to(\"body\")\n    }\n\n    fn error_message() -\u003e \u0026'static str {\n        \"Not a valid request\"\n    }\n}\n\nclass!(Server);\n\nmethods!(\n    Server,\n    itself,\n\n    fn process_request(request: Request) -\u003e RString {\n        let body = request\n            .and_then(|request| request.send(\"body\", vec![]).try_convert_to::\u003cRString\u003e())\n            .map(|body| body.to_string());\n\n        // Either request does not respond to `body` or `body` is not a String\n        if let Err(ref error) = body {\n            VM::raise(error.to_exception(), error.description());\n        }\n\n        let formatted_body = format!(\"[BODY] {}\", body.unwrap());\n\n        RString::new(\u0026formatted_body)\n    }\n);\n\n#[no_mangle]\npub extern fn initialize_server() {\n    Class::new(\"Server\", None).define(|itself| {\n        itself.def(\"process_request\", process_request);\n    });\n}\n```\n\n### Wrapping Rust data to Ruby objects\n\nWrap `Server`s to `RubyServer` objects\n\n```rust,no_run\n#[macro_use] extern crate ruru;\n#[macro_use] extern crate lazy_static;\n\nuse ruru::{AnyObject, Class, Fixnum, Object, RString, VM};\n\n// The structure which we want to wrap\npub struct Server {\n    host: String,\n    port: u16,\n}\n\nimpl Server {\n    fn new(host: String, port: u16) -\u003e Self {\n        Server {\n            host: host,\n            port: port,\n        }\n    }\n\n    fn host(\u0026self) -\u003e \u0026str {\n        \u0026self.host\n    }\n\n    fn port(\u0026self) -\u003e u16 {\n        self.port\n    }\n}\n\nwrappable_struct!(Server, ServerWrapper, SERVER_WRAPPER);\n\nclass!(RubyServer);\n\nmethods!(\n    RubyServer,\n    itself,\n\n    fn ruby_server_new(host: RString, port: Fixnum) -\u003e AnyObject {\n        let server = Server::new(host.unwrap().to_string(),\n                                 port.unwrap().to_i64() as u16);\n\n        Class::from_existing(\"RubyServer\").wrap_data(server, \u0026*SERVER_WRAPPER)\n    }\n\n    fn ruby_server_host() -\u003e RString {\n        let host = itself.get_data(\u0026*SERVER_WRAPPER).host();\n\n        RString::new(host)\n    }\n\n    fn ruby_server_port() -\u003e Fixnum {\n        let port = itself.get_data(\u0026*SERVER_WRAPPER).port();\n\n        Fixnum::new(port as i64)\n    }\n);\n\nfn main() {\n    let data_class = Class::from_existing(\"Data\");\n\n    Class::new(\"RubyServer\", Some(\u0026data_class)).define(|itself| {\n        itself.def_self(\"new\", ruby_server_new);\n\n        itself.def(\"host\", ruby_server_host);\n        itself.def(\"port\", ruby_server_port);\n    });\n}\n```\n\n### True parallelism\n\nRuru provides a way to enable true parallelism for Ruby threads by releasing GVL (GIL).\n\nIt means that a thread with released GVL runs in parallel with other threads without\nbeing interrupted by GVL.\n\nCurrent example demonstrates a \"heavy\" computation (`2 * 2` for simplicity) run in parallel.\n\n```rust,no_run\n#[macro_use] extern crate ruru;\n\nuse ruru::{Class, Fixnum, Object, VM};\n\nclass!(Calculator);\n\nmethods!(\n    Calculator,\n    itself,\n\n    fn heavy_computation() -\u003e Fixnum {\n        let computation = || { 2 * 2 };\n        let unblocking_function = || {};\n\n        // release GVL for current thread until `computation` is completed\n        let result = VM::thread_call_without_gvl(\n            computation,\n            Some(unblocking_function)\n        );\n\n        Fixnum::new(result)\n    }\n);\n\nfn main() {\n    Class::new(\"Calculator\", None).define(|itself| {\n        itself.def(\"heavy_computation\", heavy_computation);\n    });\n}\n```\n\n### Defining a new class\n\nLet's say you have a `Calculator` class.\n\n```ruby\nclass Calculator\n  def pow_3(number)\n    (1..number).each_with_object({}) do |index, hash|\n      hash[index] = index ** 3\n    end\n  end\nend\n\n# ... somewhere in the application code ...\nCalculator.new.pow_3(5) #=\u003e { 1 =\u003e 1, 2 =\u003e 8, 3 =\u003e 27, 4 =\u003e 64, 5 =\u003e 125 }\n```\n\nYou have found that it's very slow to call `pow_3` for big numbers and decided to replace the whole class\nwith Rust.\n\n```rust,no_run\n#[macro_use]\nextern crate ruru;\n\nuse std::error::Error;\nuse ruru::{Class, Fixnum, Hash, Object, VM};\n\nclass!(Calculator);\n\nmethods!(\n    Calculator,\n    itself,\n\n    fn pow_3(number: Fixnum) -\u003e Hash {\n        let mut result = Hash::new();\n\n        // Raise an exception if `number` is not a Fixnum\n        if let Err(ref error) = number {\n            VM::raise(error.to_exception(), error.description());\n        }\n\n        for i in 1..number.unwrap().to_i64() + 1 {\n            result.store(Fixnum::new(i), Fixnum::new(i.pow(3)));\n        }\n\n        result\n    }\n);\n\n#[no_mangle]\npub extern fn initialize_calculator() {\n    Class::new(\"Calculator\", None).define(|itself| {\n        itself.def(\"pow_3\", pow_3);\n    });\n}\n```\n\nRuby:\n\n```ruby\n# No Calculator class in Ruby anymore\n\n# ... somewhere in the application ...\nCalculator.new.pow_3(5) #=\u003e { 1 =\u003e 1, 2 =\u003e 8, 3 =\u003e 27, 4 =\u003e 64, 5 =\u003e 125 }\n```\n\nNothing has changed in the API of class, thus there is no need to change any code elsewhere in the app.\n\n### Replacing only several methods instead of the whole class\n\nIf the `Calculator` class from the example above has more Ruby methods, but we want to\nreplace only `pow_3`, use `Class::from_existing()`\n\n```rust,ignore\nClass::from_existing(\"Calculator\").define(|itself| {\n    itself.def(\"pow_3\", pow_3);\n});\n```\n\n### Class definition DSL\n\n```rust,no_run\nClass::new(\"Hello\", None).define(|itself| {\n    itself.const_set(\"GREETING\", \u0026RString::new(\"Hello, World!\").freeze());\n\n    itself.attr_reader(\"reader\");\n\n    itself.def_self(\"greeting\", greeting);\n    itself.def(\"many_greetings\", many_greetings);\n\n    itself.define_nested_class(\"Nested\", None).define(|itself| {\n        itself.def_self(\"nested_greeting\", nested_greeting);\n    });\n});\n```\n\nWhich corresponds to the following Ruby code:\n\n```ruby\nclass Hello\n  GREETING = \"Hello, World\".freeze\n\n  attr_reader :reader\n\n  def self.greeting\n    # ...\n  end\n\n  def many_greetings\n    # ...\n  end\n\n  class Nested\n    def self.nested_greeting\n      # ...\n    end\n  end\nend\n```\n\nSee documentation for `Class` and `Object` for more information.\n\n### Calling Ruby code from Rust\n\nGetting an account balance of some `User` whose name is John and who is 18 or 19 years old.\n\n```ruby\ndefault_balance = 0\n\naccount_balance = User\n  .find_by(age: [18, 19], name: 'John')\n  .account_balance\n\naccount_balance = default_balance unless account_balance.is_a?(Fixnum)\n```\n\n```rust,no_run\n#[macro_use]\nextern crate ruru;\n\nuse ruru::{Array, Class, Fixnum, Hash, Object, RString, Symbol};\n\nfn main() {\n    let default_balance = 0;\n    let mut conditions = Hash::new();\n\n    conditions.store(\n        Symbol::new(\"age\"),\n        Array::new().push(Fixnum::new(18)).push(Fixnum::new(19))\n    );\n\n    conditions.store(\n        Symbol::new(\"name\"),\n        RString::new(\"John\")\n    );\n\n    // Fetch user and his balance\n    // and set it to 0 if balance is not a Fixnum (for example `nil`)\n    let account_balance =\n        Class::from_existing(\"User\")\n            .send(\"find_by\", vec![conditions.to_any_object()])\n            .send(\"account_balance\", vec![])\n            .try_convert_to::\u003cFixnum\u003e()\n            .map(|balance| balance.to_i64())\n            .unwrap_or(default_balance);\n}\n```\n\n**Check out [Documentation](https://docs.rs/ruru) for many more\nexamples!**\n\n## ... and why is **FFI** not enough?\n\n - No support of native Ruby types;\n\n - No way to create a standalone application to run the Ruby VM separately;\n\n - No way to call your Ruby code from Rust;\n\n## How do I use it?\n\nWarning! The crate is a WIP.\n\nIt is recommended to use [Thermite](https://github.com/malept/thermite) gem,\na Rake-based helper for building and distributing Rust-based Ruby extensions.\n\nTo be able to use Ruru, make sure that your Ruby version is 2.3.0 or higher.\n\n1. Your local MRI copy has to be built with the `--enable-shared` option. For\n   example, using rbenv:\n\n  ```bash\n  CONFIGURE_OPTS=--enable-shared rbenv install 2.3.0\n  ```\n\n2. Add Ruru to `Cargo.toml`\n\n  ```toml\n  [dependencies]\n  ruru = \"0.9.0\"\n  ```\n\n3. Compile your library as a `dylib`\n\n  ```toml\n  [lib]\n  crate-type = [\"dylib\"]\n  ```\n\n4. Create a function which will initialize the extension\n\n  ```rust,ignore\n  #[no_mangle]\n  pub extern fn initialize_my_app() {\n      Class::new(\"SomeClass\");\n\n      // ... etc\n  }\n  ```\n\n5. Build extension\n\n  ```bash\n  $ cargo build --release\n  ```\n\n  or using Thermite\n\n  ```bash\n  $ rake thermite:build\n  ```\n\n6. On the ruby side, open the compiled `dylib` and call the function to initialize extension\n\n  ```ruby\n  require 'fiddle'\n\n  library = Fiddle::dlopen('path_to_dylib/libmy_library.dylib')\n\n  Fiddle::Function.new(library['initialize_my_app'], [], Fiddle::TYPE_VOIDP).call\n  ```\n\n7. Ruru is ready :heart:\n\n## Contributors are welcome!\n\nIf you have any questions, join Ruru on\n[Gitter](https://gitter.im/rust-ruru/general?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge)\n\n## License\n\nMIT License\n\nCopyright (c) 2015-2016 Dmitry Gritsay\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\nIcon is designed by [Github](https://github.com).\n","funding_links":[],"categories":["Development tools","Rust"],"sub_categories":["FFI"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-unsed%2Fruru","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd-unsed%2Fruru","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-unsed%2Fruru/lists"}