
Rust is blazingly fast and memory-efficient: with no runtime or garbage collector, it can power performance-critical services, run on embedded devices, and easily integrate with other languages. Rust continues the spirit of C with emphasis on code safety and performance with a compiled approach.
Raku is an open source, gradually typed, Unicode-ready, concurrency friendly programming language made for at least the next hundred years. Raku continues the spirit of Perl with interpreter-like code generation (actually on MoarVM), one-liners, shell-centric, lightweight objects and expressiveness to get code up and running fast.
Both are modern languages and come from the Linux background:
- strong C heritage via DLLs
- concurrency oriented (no GIL)
- Copy-On-Write (COW) semantics
- built-in language interop (FFI) and (NativeCall)
- no need for external libraries for interop
In the same way that Perl and C were highly complementary technologies in their heyday, so Rust and Raku are naturally, romantically destined to be linked.
Introducing raku Inline::Rust
Following the nomenclature of raku modules such as Inline::Perl5, Inline::Python, Inline::Go and so on, Inline::Rust is a newly availably “interface” to connect Raku code to Rust dynamic libraries. Unlike some of its brethren, this is rather a zen module in that it provides worked examples and helpful Dockerfile builds to speed up the process, but no code is needed. The standard Rust FFI (Foreign Function Interface) on one side – the term comes from the specification for Common Lisp, which explicitly refers to the language features for inter-language calls as such; it is also used officially by the Haskell, Rust and Python programming languages. The core Raku NativeCall on the other – for calling into dynamic libraries that follow the C calling convention
Inline::Rust is inspired by the excellent Rust FFI Omnibus by Jake Goulding.
The Rust FFI Omnibus is a collection of 6 examples of using code written in Rust from other languages. Rust has drawn a large number of people who are interested in calling native code from higher-level languages. Many nearly duplicate questions have been asked on Stack Overflow, so the Omnibus was created as a central location for easy reference. This reference already covers C, Ruby, Python, Haskell, node.js, C# and Julia – with examples for each of these languages accessing the same Rust library code.
For the purposes of brevity, since the Rust code is common for all, this article will focus on the Raku consumption:
Preamble
We need to start with some basics – follow the README.md at Inline::Rust to try it yourself:
use NativeCall;
constant $n-path = './ffi-omnibus/target/debug/foo';
Rust FFI Omnibus: Integers
Use the is native
Trait to define the external function characteristics:
sub addition(int32, int32) returns int32
is native($n-path) { * }
say addition(1, 2);
Rust FFI Omnibus: String Arguments
Raku NativeCall provides traits to control Str encoding:
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
Here we get a string back from Rust – the “free” sub means that Raku is responsible for releasing the memory. Raku Pointers can use the .deref
method to get their contents:
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
Here the for
statement is used to cover over the lack of a direct assignment to CArray … a standard raku Array has a totally different memory layout so the CArray type is provided.
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
Full disclosure – there is an open issue with this pattern:
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);
say result.x, result.y;
Rust FFI Omnibus: Objects
Here we can use a Raku class to wrap a Rust structure, note the free method is now automatically called by the Raku Garbage Collector:
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;
Summary
The raku Nativecall syntax is very straightforward and the C-heritage on both sides shows in the seamless marriage (geddit?) of types.
This results in probably the most concise and natural code on the raku side – take a look at the other examples and make your own judgement!
More to come on some practical applications of this and support for concurrency and gradual typing…
~p6steve
PS. Please do leave comments/feedback on the blog page… here
PPS. Love that raku does not enforce indentation – I can make it fit the narrow width here!
6 Comments