22 个版本 (4 个重大更改)
使用旧的 Rust 2015
0.9.3 | 2017年1月10日 |
---|---|
0.9.2 | 2016年12月10日 |
0.9.1 | 2016年11月3日 |
0.7.8 | 2016年7月9日 |
#5 在 #mri
150KB
1.5K SLoC
Ruru (Rust + Ruby)
Rust 中的原生 Ruby 扩展
你是否曾考虑过重写你的一些 慢速 Ruby 应用程序的部分?
只需将你的 Ruby 应用程序逐方法、逐类替换为 Rust,无需更改类接口或任何其他 Ruby 代码。
像 Ruby 一样简单,像 Rust 一样高效。
内容
示例
著名的 String#blank?
方法
Yehuda Katz 编写的快速的 String#blank?
实现
#[macro_use]
extern crate ruru;
use ruru::{Boolean, Class, Object, RString};
methods!(
RString,
itself,
fn string_is_blank() -> Boolean {
Boolean::new(itself.to_string().chars().all(|c| c.is_whitespace()))
}
);
#[no_mangle]
pub extern fn initialize_string() {
Class::from_existing("String").define(|itself| {
itself.def("blank?", string_is_blank);
});
}
简单的 Sidekiq 兼容服务器
安全的转换
从 0.8.0 版本开始,对内置 Ruby 类型以及自定义类型提供了安全的转换。
让我们想象我们在编写一个 HTTP 服务器。它应该处理从 Ruby 方面传递过来的请求。
任何响应 #body
方法的对象都被视为有效的请求。
#[macro_use]
extern crate ruru;
use std::error::Error;
use ruru::{Class, Object, RString, VerifiedObject, VM};
class!(Request);
impl VerifiedObject for Request {
fn is_correct_type<T: Object>(object: &T) -> bool {
object.respond_to("body")
}
fn error_message() -> &'static str {
"Not a valid request"
}
}
class!(Server);
methods!(
Server,
itself,
fn process_request(request: Request) -> RString {
let body = request
.and_then(|request| request.send("body", vec![]).try_convert_to::<RString>())
.map(|body| body.to_string());
// Either request does not respond to `body` or `body` is not a String
if let Err(ref error) = body {
VM::raise(error.to_exception(), error.description());
}
let formatted_body = format!("[BODY] {}", body.unwrap());
RString::new(&formatted_body)
}
);
#[no_mangle]
pub extern fn initialize_server() {
Class::new("Server", None).define(|itself| {
itself.def("process_request", process_request);
});
}
将 Rust 数据包装成 Ruby 对象
将 Server
包装成 RubyServer
对象
#[macro_use] extern crate ruru;
#[macro_use] extern crate lazy_static;
use ruru::{AnyObject, Class, Fixnum, Object, RString, VM};
// The structure which we want to wrap
pub struct Server {
host: String,
port: u16,
}
impl Server {
fn new(host: String, port: u16) -> Self {
Server {
host: host,
port: port,
}
}
fn host(&self) -> &str {
&self.host
}
fn port(&self) -> u16 {
self.port
}
}
wrappable_struct!(Server, ServerWrapper, SERVER_WRAPPER);
class!(RubyServer);
methods!(
RubyServer,
itself,
fn ruby_server_new(host: RString, port: Fixnum) -> AnyObject {
let server = Server::new(host.unwrap().to_string(),
port.unwrap().to_i64() as u16);
Class::from_existing("RubyServer").wrap_data(server, &*SERVER_WRAPPER)
}
fn ruby_server_host() -> RString {
let host = itself.get_data(&*SERVER_WRAPPER).host();
RString::new(host)
}
fn ruby_server_port() -> Fixnum {
let port = itself.get_data(&*SERVER_WRAPPER).port();
Fixnum::new(port as i64)
}
);
fn main() {
let data_class = Class::from_existing("Data");
Class::new("RubyServer", Some(&data_class)).define(|itself| {
itself.def_self("new", ruby_server_new);
itself.def("host", ruby_server_host);
itself.def("port", ruby_server_port);
});
}
真正的并行性
Ruru 提供了一种方法,通过释放 GVL (GIL) 来启用 Ruby 线程的真正并行性。
这意味着一个释放了 GVL 的线程可以与其他线程并行运行,而不会被 GVL 打断。
当前示例演示了一个“重”计算(为了简单起见,为 2 * 2
)并行运行。
#[macro_use] extern crate ruru;
use ruru::{Class, Fixnum, Object, VM};
class!(Calculator);
methods!(
Calculator,
itself,
fn heavy_computation() -> Fixnum {
let computation = || { 2 * 2 };
let unblocking_function = || {};
// release GVL for current thread until `computation` is completed
let result = VM::thread_call_without_gvl(
computation,
Some(unblocking_function)
);
Fixnum::new(result)
}
);
fn main() {
Class::new("Calculator", None).define(|itself| {
itself.def("heavy_computation", heavy_computation);
});
}
定义一个新类
假设你有一个 Calculator
类。
class Calculator
def pow_3(number)
(1..number).each_with_object({}) do |index, hash|
hash[index] = index ** 3
end
end
end
# ... somewhere in the application code ...
Calculator.new.pow_3(5) #=> { 1 => 1, 2 => 8, 3 => 27, 4 => 64, 5 => 125 }
你发现调用 pow_3
对于大数字来说非常慢,因此决定用 Rust 替换整个类。
#[macro_use]
extern crate ruru;
use std::error::Error;
use ruru::{Class, Fixnum, Hash, Object, VM};
class!(Calculator);
methods!(
Calculator,
itself,
fn pow_3(number: Fixnum) -> Hash {
let mut result = Hash::new();
// Raise an exception if `number` is not a Fixnum
if let Err(ref error) = number {
VM::raise(error.to_exception(), error.description());
}
for i in 1..number.unwrap().to_i64() + 1 {
result.store(Fixnum::new(i), Fixnum::new(i.pow(3)));
}
result
}
);
#[no_mangle]
pub extern fn initialize_calculator() {
Class::new("Calculator", None).define(|itself| {
itself.def("pow_3", pow_3);
});
}
Ruby
# No Calculator class in Ruby anymore
# ... somewhere in the application ...
Calculator.new.pow_3(5) #=> { 1 => 1, 2 => 8, 3 => 27, 4 => 64, 5 => 125 }
类的 API 没有发生变化,因此无需更改应用程序其他地方的任何代码。
只替换几个方法而不是整个类
如果示例中的 Calculator
类有更多的 Ruby 方法,但只想替换 pow_3
,请使用 Class::from_existing()
Class::from_existing("Calculator").define(|itself| {
itself.def("pow_3", pow_3);
});
类定义 DSL
Class::new("Hello", None).define(|itself| {
itself.const_set("GREETING", &RString::new("Hello, World!").freeze());
itself.attr_reader("reader");
itself.def_self("greeting", greeting);
itself.def("many_greetings", many_greetings);
itself.define_nested_class("Nested", None).define(|itself| {
itself.def_self("nested_greeting", nested_greeting);
});
});
这对应于以下 Ruby 代码
class Hello
GREETING = "Hello, World".freeze
attr_reader :reader
def self.greeting
# ...
end
def many_greetings
# ...
end
class Nested
def self.nested_greeting
# ...
end
end
end
有关 Class
和 Object
的更多信息,请参阅文档。
从 Rust 调用 Ruby 代码
获取名为 John 且 18 或 19 岁的 User
的账户余额。
default_balance = 0
account_balance = User
.find_by(age: [18, 19], name: 'John')
.account_balance
account_balance = default_balance unless account_balance.is_a?(Fixnum)
#[macro_use]
extern crate ruru;
use ruru::{Array, Class, Fixnum, Hash, Object, RString, Symbol};
fn main() {
let default_balance = 0;
let mut conditions = Hash::new();
conditions.store(
Symbol::new("age"),
Array::new().push(Fixnum::new(18)).push(Fixnum::new(19))
);
conditions.store(
Symbol::new("name"),
RString::new("John")
);
// Fetch user and his balance
// and set it to 0 if balance is not a Fixnum (for example `nil`)
let account_balance =
Class::from_existing("User")
.send("find_by", vec![conditions.to_any_object()])
.send("account_balance", vec![])
.try_convert_to::<Fixnum>()
.map(|balance| balance.to_i64())
.unwrap_or(default_balance);
}
查看更多示例的 文档!
... 以及为什么 FFI 不够用?
-
不支持原生 Ruby 类型;
-
无法创建独立的应用程序来单独运行 Ruby 虚拟机;
-
无法从 Rust 中调用 Ruby 代码;
如何使用它?
警告!该软件包是工作进展中的(WIP);
建议使用 Thermite 钩子,这是一个基于 Rake 的助手,用于构建和分发基于 Rust 的 Ruby 扩展;
要使用 Ruru,请确保您的 Ruby 版本为 2.3.0 或更高;
- 您的本地 MRI 复制必须使用
--enable-shared
选项构建。例如,使用 rbenv
CONFIGURE_OPTS=--enable-shared rbenv install 2.3.0
- 将 Ruru 添加到
Cargo.toml
[dependencies]
ruru = "0.9.0"
- 将库编译为
dylib
[lib]
crate-type = ["dylib"]
- 创建一个初始化扩展的函数
#[no_mangle]
pub extern fn initialize_my_app() {
Class::new("SomeClass");
// ... etc
}
- 构建扩展
$ cargo build --release
或使用 Thermite
$ rake thermite:build
- 在 Ruby 端,打开编译后的
dylib
并调用初始化扩展的函数
require 'fiddle'
library = Fiddle::dlopen('path_to_dylib/libmy_library.dylib')
Fiddle::Function.new(library['initialize_my_app'], [], Fiddle::TYPE_VOIDP).call
- Ruru 准备就绪 ❤️
欢迎贡献者!
如果您有任何问题,请加入 Ruru 在 Gitter
许可证
MIT 许可证
版权所有 (c) 2015-2016 Dmitry Gritsay
特此授予任何获得此软件及其相关文档副本(以下简称“软件”)的人免费使用软件的权利,不受任何限制,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或出售软件的副本,并允许软件的提供方使用软件,前提是遵守以下条件
上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。
软件按“原样”提供,不提供任何明示或暗示的保证,包括但不限于适销性、特定用途的适用性和非侵权性。在任何情况下,作者或版权所有者均不对任何索赔、损害或其他责任承担责任,无论此类责任是基于合同、侵权或其他方式,是否源于、因之而产生或与此软件或软件的使用或其他交易有关。
图标由 Github 设计。
依赖项
~74KB