Wait the light to fall

Raku and Rust Ffi

焉知非鱼

Raku and Rust Ffi

Rust 的速度快得惊人,内存效率高:没有运行时或垃圾收集器,它可以为性能关键的服务提供动力,在嵌入式设备上运行,并容易与其他语言集成。Rust 延续了 C 语言的精神,采用编译的方式强调代码安全和性能。

Raku 是一种开源的、渐进类型化的、Unicode 可用的、并发友好的编程语言,至少在未来的一百年里是如此。Raku 延续了 Perl 的精神,具有类似于解释器的代码生成(实际上是在 MoarVM上),单行线,以 shell 为中心,轻量级的对象和表现力,可以让代码快速启动和运行。

这两种语言都是现代语言,都来自于 Linux 背景:

  • 通过 DLLs 带来的强大 C 语言遗产
  • 面向并发(没有 GIL)
  • 写时复制(COW)语义
  • 内置语言互操作(Rust 的 FFI)和(Raku 的 NativeCall)
  • 不需要外部库来实现互操作

就像 Perl 和 C 在它们的全盛时期是高度互补的技术一样,Rust 和 Raku 也自然地、浪漫地注定要联系在一起。

Inline::Rust 入门 #

遵循 Raku 模块的命名方式,如 Inline::Perl5Inline::Python、Inline::Go 等,Inline::Rust 是一个新的可用"接口",用于连接 Raku 代码和 Rust 动态库。与它的一些兄弟不同的是,这是一个相当有禅意的模块,它提供了工作实例和有用的 Dockerfile 构建来加速这个过程,但不需要代码。一边是标准的 Rust FFI(Foreign Function Interface) - 这个术语来自于 Common Lisp 的规范,它明确提到了语言间调用的这种语言特性;它也被 Haskell、Rust 和 Python 编程语言正式使用。另一方面,核心的 Raku NativeCall - 用于调用到遵循 C 语言调用惯例的动态库中。

Inline::Rust 的灵感来自于 Jake Goulding 的优秀 Rust FFI Omnibus

Rust FFI Omnibus 收集了 6 个使用其他语言的 Rust 代码的例子。Rust 吸引了一大批对从高级语言调用本地代码感兴趣的人。在 Stack Overflow 上有许多几乎重复的问题,因此创建了 Omnibus 作为一个中心位置,以方便参考。这个参考资料已经涵盖了 C、Ruby、Python、Haskell、node.js、C# 和 Julia - 其中每一种语言都有访问相同 Rust 库代码的例子。

为了简洁起见,由于 Rust 代码对所有语言都是通用的,本文将重点介绍 Raku 专用的代码。

序言 #

我们需要从一些基础知识开始 – 跟着 Inline::Rust 的 README.md 来自行尝试。

use NativeCall; 

constant $n-path = './ffi-omnibus/target/debug/foo';

Rust FFI Omnibus: Integers #

使用 is native Trait 来定义外部函数特质:

sub addition(int32, int32) returns int32 
    is native($n-path) { * }

say addition(1, 2);

Rust FFI Omnibus: String Arguments #

Raku NativeCall 提供了控制 Str 编码的 traits:

sub how_many_characters(Str is encoded('utf8')) returns int32 
    is native($n-path) { * }

say how_many_characters("göes to élevên");

Rust FFI Omnibus: String Return Values #

这里我们从 Rust 得到一个字符串 – 带 “free” 的子例程意味着 Raku 负责释放内存。Raku 指针可以使用 .deref 方法来获取其内容:

sub theme_song_generate(uint8) returns Pointer[Str] is encoded('utf8') 
    is native($n-path) { * }
sub theme_song_free(Pointer[Str]) 
    is native($n-path) { * }

my \song = theme_song_generate(5);
say song.deref;
theme_song_free(song);

Rust FFI Omnibus: Slice Arguments #

这里 for 语句用来掩盖对 CArray 直接赋值的缺失……标准的 Raku 数组有完全不同的内存布局,所以 Raku 提供了 CArray 类型。

sub sum_of_even(CArray[uint32], size_t) returns uint32 
    is native($n-path) { * }

my @numbers := CArray[uint32].new;
@numbers[$++] = $_ for 1..6; 

say sum_of_even( @numbers, @numbers.elems );

Rust FFI Omnibus: Tuples #

全面披露 - 这种模式有一个公开的问题。

class Tuple is repr('CStruct') {
    has uint32 $.x;
    has uint32 $.y;
}
sub flip_things_around(Tuple) returns Tuple 
    is native($n-path) { * }

my \initial = Tuple.new( x => 10, y => 20 );
my \result  = flip_things_around(initial);
dd result;
say result.x, result.y;

它还打印出了内存地址。

Rust FFI Omnibus: Objects #

这里我们可以使用 Raku 类来包装一个 Rust 结构,注意带 free 的方法现在被 Raku 垃圾收集器自动调用了:

class ZipCodeDatabase is repr('CPointer') {
    sub zip_code_database_new() returns ZipCodeDatabase 
        is native($n-path) { * }
    sub zip_code_database_free(ZipCodeDatabase)         
        is native($n-path) { * }
    sub zip_code_database_populate(ZipCodeDatabase)     
        is native($n-path) { * }
    sub zip_code_database_population_of(ZipCodeDatabase, Str 
        is encoded('utf8'))returns uint32 is native($n-path) { * }

    method new {
        zip_code_database_new
    }

    # Free data when the object is garbage collected.
    submethod DESTROY {        
        zip_code_database_free(self);
    }

    method populate {
        zip_code_database_populate(self)
    }

    method population_of( Str \zip ) {
        zip_code_database_population_of(self, zip);
    }
}

my \database = ZipCodeDatabase.new;
database.populate;

my \pop1 = database.population_of('90210');
my \pop2 = database.population_of('20500');
say pop1 - pop2;

总结 #

Raku Nativecall 的语法非常简单,双方的 C 语言遗产都体现在类型的无缝连接上。

这可能是 Raku 这端最简洁和自然的代码了 - 看看其他的例子,然后你自己判断吧!

更多关于这个的实际应用,以及对并发和渐进类型的支持,将在未来推出…

~p6steve

PS. 请在博客页面上留下评论/反馈…点这儿

PPS. 我喜欢 Raku 不强制缩进的做法 - 我可以让它适应博客的狭窄宽度!

PPPS. 在这里运行博客中的 demo:

git clone https://github.com/p6steve/raku-Inline-Rust.git
cd raku-Inline-Rust/ffi-omnibus
cargo build
cd ..
raku ffi-omnibus.raku