Creating a Rust dylib we can call from Xojo

First – download and install Rust if you dont have it. You’ll need to verify that it is installed correctly.

I’ve been using cargo, the Rust package manager, to configure new projects to work on. And I have all my projects in a directory where I can access them easily.

Most of the work is in Terminal so start up a terminal window.

Change to your directory where you want the project to be saved. For me this is in ~/RustProjects

cd ~/RustProjects

Create a new project using cargo – I called mine FirstRustLib

cargo new FirstRustLib

This will create a new directory with all the bits we’ll need inside it1. Inside is a Cargo.toml file and a src directory. The TOML file is a project config file. It lets you specify all kinds of dependencies on other Rust libraries, settings and all kinds of other bits (but thats an entirely different topic)

Open the Cargo.toml file as we do need to make some changes to it. Any plain text editor (BBEdit, TextWrangler, even TextEdit will do just fine)

In the TOML file you will see that its already preconfigured with several items that are used to identify the pakage. We need to add a new section to this to tell cargo that instead of a normal Rust application we want this package to be compiled as a dylib (not a Rust library as thats only usable within Rust)

We need to add the following

[lib]
crate_type=["cdylib"]
name="our_rust"

the “crate type” entry tells Cargo what kind of output this project is going to build. In this case its a normal C style dylib. The name entry tell Cargo what to name the built dylib (it will end up being named starting with lib – in this case libour_rust.dylib)

And thats is so save these changes.

The way we created the new project Cargo has created a new file in the src directory named main.rs. For a library this should be named lib.rs – otherwise when we build using Cargo it will complain. Rename main.rs to lib.rs

If you change into the directory FirstRustLib

cd FirstRustLib

and run a build

cargo build

you will build the project as it sits now. It wont build correctly because lib.rs actually contains code that is suited to an application, not a dylib, so you will get errors like

 Compiling FirstRustLib v0.1.0 (/Users/npalardy/RustProjects/FirstRustLib)
warning: function is never used: `main`
 --> src/lib.rs:1:4
  |
1 | fn main() {
  |    ^^^^
  |
  = note: `#[warn(dead_code)]` on by default

    Finished dev [unoptimized + debuginfo] target(s) in 1.46s

but this is entirely expected

So now, on to defining our first function for our dylib, that will be exposed. We’re not going to do anything crazy just yet. Something simple like adding two integers and returning the result.

Open lib.rs and remove ALL the code it in at present. Replace it with the following :

#[no_mangle]
pub extern fn add_numbers(number1: i32, number2: i32) -> i32 {
    println!("Hello from rust!");
    number1 + number2
}

And save this.

The first line, #[no_mangle], tells the Rust compiler NOT to mangle the names so they are exported as is. You need to do this otherwise the actual exported name will be something you have to guess at in Xojo and thats not helpful.

The next line is quite terse but can be read from left to right as :

pub – this item is accessible to outside code (other Rust code even)

extern – makes this item usable by external code (in our case Xojo)

fn – function (as opposed to a sub. In Xojo this would just be a method that returns a result)

add_numbers– this is the name of the function we’re writing

(number1: i32, number2: i32) – the parameters. number1 and number 2 are int32

-> i32 – this odd little syntax is equivalent to Xojo “as Int32” – the return type

And the entire function is surrounded by an opening { and closing }

I’m sure some folks eyes have glazed over by now – the seemingly C style syntax does that for some 🙂

And the rest of the code in the function prints a message “Hello World” and then adds the two numbers together. Not there is not explicit “return” of the value like in Xojo.

And that simple function is all the Rust code we need. Save it and build

cd ~/RustProjects/FirstRustLib/
cargo build

If you have no syntax errors you should see something like

    Finished dev [unoptimized + debuginfo] target(s) in 0.04s

And there will be new directories that have been created. Now you should have a target directory. Inside this will be a debug directory (we did not tell cargo to build a release build so it builds a debug one by default)

Inside debug are several other dirs as well as our built dylib

On to the Xojo code to use this dylib we created

Start a new Xojo desktop project. I’m using 2019r1.1 so this should work for older versions and newer versions as well.

Add a copy file step to the macOS target. Drag in the dylib in to the copy file step and set it to copy the dylib into the Frameworks directory on macOS.

To use the dylib we do need to write a declare into it. But since we used really simple types and Xojo has exact matches for the Rust one we can get away with a simple declare.

Since we copied the dylib into the FRameworks directory in the macOS bunmdle we will need to tell Xojo to locate th dylib based on the executables location like

Soft Declare Function add_numbers Lib "@executable_path/../Frameworks/libour_rust.dylib" (number1 As Int32, number2 As Int32) As Int32 

@executable_path is special for macOS and literally means to use the current executables_path and then a relative path from there.

For Windows you may want to configure the build step to copy the dll to the Contents folder and then the declare would be

Soft Declare Function add_numbers Lib "libour_rust.dll" (number1 As Int32, number2 As Int32) As Int32 

Then the code to use the dylib/dll is really simple – just like any method from Xojo

Dim a As Int32
Dim b As Int32
Dim result As Int32

a = 123
b = 456

result = add_numbers(a,b)

Nothing fancy just a simple call into a Rust dylib/dll we made.

And of course the obligatory “ok WHY use Rust ?”

Turns out that Rust is, by design, deliberately safer to use than many languages.

It was deliberately designed to be safe, and concurrent. And it doesn’t use garbage collection.

And there are a huge number of available “libraries” you can use with it.

It can cross compile to a HUGE number of targets that keeps getting more and more “Tier 1 targets”

  1. there are other ways to initialize a new project using Cargo but I went this way to illustrate the steps required.