10 个版本
0.0.9 | 2019 年 5 月 29 日 |
---|---|
0.0.8 | 2019 年 5 月 23 日 |
#148 在 FFI
每月 24 次下载
310KB
6.5K SLoC
为 Rust 提供Ruby 的 C API 的高层(或零成本)绑定。
索引
特性
-
性能
Rosy 允许您编写性能最优的代码,直接使用 C API 也不会提高性能。换句话说,它提供了 零成本抽象。然而,Rosy 的并非所有 安全 抽象都是零成本的。有时,只能通过编写一些
unsafe
代码来实现,因为 Rosy 无法了解程序状态的一些方面。-
例如,
Object::call
将通过protected
函数族捕获任何抛出的 Ruby 异常。另一方面,Object::call_unchecked
将允许任何抛出的异常传播(在 Rust 中这会导致段错误),除非使用protected
。通过
protected
检查异常会有一定的开销。最好将多个未检查的异常抛出函数实例包装起来。这可以让你的代码中的速度瓶颈减少。 -
如果在异常检查的代码中已知不会发生
panic!
,那么调用protected_no_panic
将会以牺牲安全性的代价产生更少的指令。传递给此函数的FnOnce
在 FFI 上下文中被调用;因此,这里的恐慌是未定义行为。在正常protected
调用中的恐慌会被安全地捕获,并且堆栈回溯被正确处理。
注意,后缀为
unsafe
的unsafe
函数始终有一个安全的对应版本。在尝试使用unsafe
函数之前,考虑使用这些代替品,并对您的代码进行性能分析,以找出是否实际上需要它们。 -
-
强大类型
Rosy 充分利用 Rust 的类型系统。
-
Rosy 使得某些 Ruby 类型对封装类型泛型化
-
Hash<K, V>
对Object
键和值进行泛型化,两者都默认为AnyObject
。 -
Class<O>
对它可以通过Class::new_instance
实例化的Object
类型进行泛型化。
-
通过
Class::def_method
或def_method!
定义方法
-
-
安全性: *尽可能
Rosy 在 Ruby 的 C API 的大部分内容上暴露了安全的抽象。无论何时无法做到这一点,此类功能都被标记为
unsafe
,并附带关于安全使用的文档说明。遗憾的是,由于 Ruby 的 C API 的固有性质,安全通常不易实现,需要做出一些妥协。
-
许多 Ruby 函数可以引发异常,这会在 Rust-land 中触发 段错误。😓
可能会抛出异常的函数被标记为
unsafe
,或者通过protected
提供的安全异常检查等效功能。然而,检查异常会有性能开销。 -
Ruby的垃圾回收器会回收那些引用不在栈上的对象,除非它们被
mark
标记。这可能会导致可能的use after free。在包装Rust数据时,正确实现Rosy::mark
非常重要。
-
安装
Rosy需要cargo
和现有的Ruby安装
rosy
crate可以在crates.io上找到,可以通过在项目的Cargo.toml
中添加以下内容来使用
[dependencies]
rosy = "0.0.9"
Rosy的功能仅适用于某些Ruby版本。以下功能目前可以启用
ruby_2_6
例如
[dependencies.rosy]
version = "0.0.9"
features = ["ruby_2_6"]
最后将其添加到crate根目录(main.rs
或 lib.rs
)
extern crate rosy;
构建
Rosy可以通过简单地运行来编译
cargo build
它将自动尝试通过当前ruby
安装来查找动态库。
要启用静态链接,请指定static
特性标志
[dependencies.rosy]
version = "0.0.9"
features = ["static"]
要使用特定的Ruby安装,您可以执行以下任意一项
-
设置
ROSY_RUBY=path/to/ruby
这必须指向一个可执行文件。
-
设置
ROSY_RUBY=client:version
。例如-
ROSY_RUBY=rvm:2.6.0
-
ROSY_RUBY=rbenv:2.5.0
如果未提供
:
部分,则使用ROSY_RUBY_VERSION
来获取版本号。例如ROSY_RUBY=rvm ROSY_RUBY_VERSION=2.6.0 cargo build
-
使用
Rosy允许您以在Rust中非常自然的方式执行许多对Ruby对象的操作。
管理 Ruby 虚拟机
在执行任何操作之前,必须通过vm::init
来初始化虚拟机。
rosy::vm::init().expect("Could not initialize Ruby");
一旦Ruby操作完成,您可以通过vm::destroy
永久清理其资源。
if let Err(code) = unsafe { rosy::vm::destroy() } {
std::process::exit(code);
}
请注意,一旦销毁了虚拟机,您将无法再使用Ruby。
调用 Ruby 方法
使用Object::call
,可以在接收对象上调用任何方法
use rosy::String;
let string = String::from("hello\r\n");
string.call("chomp!").unwrap();
assert_eq!(string, "hello");
要传递参数,请使用Object::call_with
use rosy::{Array, Integer, Object};
let array: Array<Integer> = (10..20).collect();
let args: &[Integer] = &[1.into(), 5.into(), 9.into()];
let values = array.call_with("values_at", args).unwrap();
assert_eq!(values, [11, 15, 19][..]);
定义 Ruby 方法
为了在Ruby的String
类上定义一个对UTF-8敏感的方法blank?
,可以使用非常简单的宏def_method!
。这允许定义一个函数,该函数以类(在本例中为String
)作为接收者来接收类型对象。
use rosy::prelude::*;
let class = Class::of::<String>();
rosy::def_method!(class, "blank?", |this: String| {
this.is_whitespace()
}).unwrap();
let string = String::from(" \r\n");
let result = string.call("blank?");
assert_eq!(result.unwrap(), true);
虽然这个宏可能感觉有些神奇,但实际上它只是Class::def_method
的零成本包装,后者本身是rb_define_method_id
的低成本抽象。为了将抽象成本降低到绝对零,请使用def_method_unchecked!
。
定义 Ruby 类
定义一个新的类相当直接
let my_object = Class::def("MyObject").unwrap();
尝试定义一个现有的类将导致错误
let array = Class::def("Array")
.unwrap_err()
.existing_class()
.unwrap();
assert_eq!(array, Class::array());
如果它不是一个内置类,则应调用Class::get
let my_object = Class::get("MyObject").unwrap();
如果不确定类是否已经存在,那么有最好的两种世界:使用Class::get_or_def
。如果类不存在,它将定义具有给定名称的类。
let my_object = Class::get_or_def("MyObject").unwrap();
要在类或模块的命名空间中定义类,请使用Mixin::def_class
。
定义 Ruby 子类
Class::subclass
方法允许创建一个新的类,该类从方法接收者的类继承而来。
let sub_object = my_object.subclass("MyObjectChild").unwrap();
assert!(sub_object < my_object);
要在类或模块的命名空间中定义子类,请使用Mixin::def_subclass
。
捕获 Ruby 异常
Rust代码可以非常容易地通过Ruby异常进行保护。
use rosy::{Object, String, protected};
let string = String::from("¡Hola!");
let result = protected(|| unsafe {
string.call_unchecked("likes_pie?")
});
assert!(result.unwrap_err().is_no_method_error());
平台支持
Rosy在构建阶段使用aloxide
查找和链接Ruby。因此,Rosy的平台支持完全依赖于它。修复链接问题(或未来构建Ruby)的更改应提交给该库,以便用于此。
要在本地与Rosy和aloxide结合使用,请按照如下修改Rosy的Cargo.toml
:
[build-dependencies]
aloxide = { path = "path/to/aloxide", version = "0.0.8", default-features = false }
库比较
与大多数技术一样,Rosy不是第一个这样的技术。
Rosy 与 Helix
Helix是一个建立在宏之上的Rust库。与Ruby运行时的交互是通过一个ruby!
宏来完成的,该宏具有一个介于Rust和Ruby语法之间的DSL。对于来自Ruby的人,他们感觉非常熟悉。然而,来自Rust的人可能会觉得这个宏有点太神奇了。
与Helix不同,Rosy的每个宏都有一个纯粹通过类型、特性和函数采取的替代方法。Rosy旨在方便且高级,同时尝试不隐藏可以让你编写更好优化的代码的低级细节。这与Rust作为高级语言的方式并行。
Rosy 与 Rutie
Rutie 是一个Rust库,它试图比Helix更少使用魔法。它是基于 ruru 的作品延续,而ruru自2017年底以来不再维护。Rutie通过将Ruby类暴露为Rust struct
来采取了一种卓越的方法来包装Ruby的C API,这在一定程度上启发了Rosy的布局和设计。
然而,与Rutie不同,Rosy没有暴露底层的C绑定。理由是,如果Rosy缺少某些功能,应该通过提交一个 issue 或通过提交一个包含实现的 pull request 来将其添加到核心库中。
此外,与Rutie不同,Rosy将所有抛出异常的函数标记为 unsafe
。从Rust代码中未处理Ruby异常会导致 段错误。一些人选择用Rust而不是C的主要原因之一就是想避免这些错误。Rust的哲学是安全代码不应该能够触发段错误。就像Rutie一样,Rosy允许Rust代码通过 protected
函数来防止抛出的异常。
作者
Rosy是 Ocean 软件包管理器的一部分。
并且由以下个人贡献者开发
-
创建者: Nikolai Vazquez
许可
该项目根据您的选择,在 MIT许可证 或 Apache许可证2.0 下提供。
请参阅 LICENSE.md
。
恭喜你走到了这里!ʕノ•ᴥ•ʔノ🌹
依赖项
~210KB