10 个版本

0.0.9 2019 年 5 月 29 日
0.0.8 2019 年 5 月 23 日

#148FFI

每月 24 次下载

MIT/Apache

310KB
6.5K SLoC

rosy banner
travis badge lines of code crates.io downloads API docs
platforms MIT or Apache 2.0

为 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 调用中的恐慌会被安全地捕获,并且堆栈回溯被正确处理。

    注意,后缀为 unsafeunsafe 函数始终有一个安全的对应版本。在尝试使用 unsafe 函数之前,考虑使用这些代替品,并对您的代码进行性能分析,以找出是否实际上需要它们。

  • 强大类型

    Rosy 充分利用 Rust 的类型系统。

  • 安全性: *尽可能

    Rosy 在 Ruby 的 C API 的大部分内容上暴露了安全的抽象。无论何时无法做到这一点,此类功能都被标记为 unsafe,并附带关于安全使用的文档说明。

    遗憾的是,由于 Ruby 的 C API 的固有性质,安全通常不易实现,需要做出一些妥协。

    • 许多 Ruby 函数可以引发异常,这会在 Rust-land 中触发 段错误。😓

      可能会抛出异常的函数被标记为 unsafe,或者通过protected 提供的安全异常检查等效功能。然而,检查异常会有性能开销。

    • Ruby的垃圾回收器会回收那些引用不在栈上的对象,除非它们被mark 标记。这可能会导致可能的use after free。在包装Rust数据时,正确实现Rosy::mark非常重要。

安装

Rosy需要cargo 和现有的Ruby安装

  • cargo可以通过rustup安装

  • Ruby可以通过rvmrbenv 或您喜欢的包管理器安装

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.rslib.rs

extern crate rosy;

构建

Rosy可以通过简单地运行来编译

cargo build

它将自动尝试通过当前ruby安装来查找动态库。

要启用静态链接,请指定static 特性标志

[dependencies.rosy]
version = "0.0.9"
features = ["static"]

要使用特定的Ruby安装,您可以执行以下任意一项

使用

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 软件包管理器的一部分。

Support oceanpkg on Patreon

并且由以下个人贡献者开发

许可

该项目根据您的选择,在 MIT许可证Apache许可证2.0 下提供。

请参阅 LICENSE.md


恭喜你走到了这里!ʕノ•ᴥ•ʔノ🌹

返回顶部

依赖项

~210KB