Day 13 – A Little R&R

A Little R&R

camelialoveferris

Introduction

Raku is a really nice language. Versatile, expressive, fast, dwimmy. The only problem I sometimes have with it is that it can be a little slow. Fortunately that can easily be solved by the NativeCall interface, which makes it easy to call C code in your Raku program. Now, as nice as C is, it is a rather old language with some limitations. A newer language that can fill its niche is known as Rust. I’ll show some examples of having Raku talk to Rust.

FFI

Rust code can be called from other languages using the FFI standard. FFI stands for “Foreign Function Interface” and allows you to export a Rust library as a standard shared library (a .so file on Linux or .dll on Windows). This is done by adding the following section in your Cargo.toml:

[lib]
crate-type = ["cdylib"]

After adding this section you will find the libraries in your target/debug or target/release folder when you build the library with Cargo. Also be sure to add the libc dependency to get access to standard C types.

Primitives

We can use the same primitive types as in C: numbers (and chars) and arrays.

Numbers and chars

Rust:

#[no_mangle]
pub extern fn addition(a: u32, b:32) -> u32 {
        a + b
}

Raku:

use NativeCall;
sub addition(uint32, uint32) returns uint32 is native('foo') { * }

Note the #[no_mangle], this keeps the name of the function unchanged in the final library file. While Rust has standardized name mangling (contrary to C++, where the name mangling is platform-dependent), it is still nice to call a function with it’s original name.

Arrays and strings

Rust:

use std::ffi::CStr;
use std::os::raw::c_char;

#[no_mangle]
pub unsafe extern fn count_chars(s: *const c_char) -> u32 {
        CStr::from_ptr(s).to_str().unwrap().chars().count() as u32
}

#[no_mangle]
pub extern fn lunch() -> *mut c_char {
        let c_string = CString::new("🌮🍚").expect("CString::new failed");
        c_string.into_raw()
}

#[no_mangle]
pub unsafe extern fn free_lunch(ptr: *mut c_char) {
        let _ = CString::from_raw(ptr);
}

Raku:

sub count_chars(Str is encoded('utf8')) returns uint32 is native ('foo') { * }
sub lunch() returns CArray[uint8] is native('foo') { * }
sub free_lunch(CArray[uint8]) is native('foo') { * }

Rust has first class support for UTF-8, making it a great fit for Raku. Using CString also guarantees to add a null byte at the end of the string, so you can get the significant bytes by looping until the AT-POS() value equals 0… if, that is, you choose to return an array rather than populate it.

Structs

Rust:

use std::mem::swap;

#[repr(C)]
pub struct Point {
    x: f32,
    y: f32,
}

impl Point {
        fn print(&self) {
                println!("x: {}, y: {}", self.x, self.y);
        }
}

#[no_mangle]
pub unsafe extern "C" fn flip(p: *mut Point) {
    swap(&mut (*p).x, &mut (*p).y);
        (*p).print();
}

Raku:

class Point is repr('CStruct') {
    has num32 $.x;
    has num32 $.y;
}

sub flip(Pointer[Point]) is native('./librnr.so') { * }

sub flipper {
    my Point $p .= new(x => 3.Num, y => 4.Num);
    say "x: ", $p.x, ", y: ", $p.y;
    flip(nativecast(Pointer[Point], $p));
}

Rust separates objects into structs (which we are all familiar with), and traits, which are kind of like roles in Raku.

Concurrency

Rust:

#[no_mangle]
pub extern "C" fn multithread(count: i32) {
    let threads: Vec<_> = (1..8)
        .map(|id| {
            thread::spawn(move || {
                println!("Starting thread {}", id);
                let mut x = 0;
                for y in 0..count {
                    x += y;
                    println!("Thread {}, {}/{}: {}", id, y, count, x);
                }
            })
        })
        .collect();

    for t in threads {
        t.join().expect("Could not join a thread!");
    }
}

Raku:

sub multithread(int32) is native('./librnr.so') { * }

sub multi-multithread {
    my @numbers = (3_000..50_000).pick(10);

    my @promises;

    for @numbers -> $n {
        push @promises, start {
            multithread($n);
            True;
        };
    }

    await Promise.allof(@promises);
}

Rust and Raku both have first-class concurrency support. This allows you to easily tweak your programs to get the highest possible performance.

Closing statement

These were some examples of interactions between Rust and Raku, two of the most promising languages when looking to the future. If you found this interesting, be sure to check out Andrew Shitov’s A Language a Day articles. Thanks for reading and happy holidays.

Published by tmtvl

Martial artist and Linux user. Does a little Java development with a helping of Perl on the side.

2 thoughts on “Day 13 – A Little R&R

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.