Using a C Library with Rust

Why?

The most common thing is to use an embedded library that exists in C and it’s too big to be rewritten in Rust, my case I needed to use ALSA for a project, so here I am, I’ve used ALSA before with Go in another project, which was kinda straight forward, just add the files, link some addidional files, and you’re good to go (not intended punch), but with Rust you need to do some more stuff when linking non standard libraries.

One way to do it, is to use Bindgen, which exports the C types directly into Rust types, then you can go from there, me, myself I don’t like generated code in my project (If you’re not new here, yes I do enjoy templ, but that’s a different story…), so I went to use CMake with cmake-rs on top, that way I can mess around as much as I like with C files, and have the perfect level of abstraction without having to dance with C types in Rust (the bindgen way).

So, let’s begin, we’re gonna need the following:

Hello C

We’re gonna be writing a super abstract layer to ALSA, where literally the only exposed functions from C are init_alsa, destroy_alsa and play_frequency, where their names really give them away, the we’re gonna use the ALSA abstraction to play the first 29 notes of The Rains of Castamere.

Start by creating a Rust project

cargo new rust-of-castamere

Then create a directory, preferbly at the root level of the project so there’s separation between C and Rust files.

mkdir libalsa

And create the CMake descriptor file libalsa/CMakeLists.txt

# libalsa/CMakeLists.txt
# the coolest cmake at the time of writing this post was 3.6
cmake_minimum_required(VERSION 3.6)
# set project name
project(LibAlsa)
# define the library (project) as static
add_library(alsa STATIC alsa.c)
# add the project files to the target rust files
install(TARGETS alsa DESTINATION .)

Now create alsa.h and alsa.c to test if this thing actually works before adding ALSA to the mix.

And make sure to extern your C function at least in the header file.

// libalsa/alsa.h
#ifndef ALSA_RS_H
#define ALSA_RS_H

#include <stdio.h>
#include <math.h>

extern void greet_3(char* name);

#endif
// libalsa/alsa.c
#include "alsa.h"

void greet_3(char* name) {
    printf("Hello %s, some number: %f\n", name, pow(2, 3.3));
}

Now back to Rust, add cmake as a build dependency

cargo add cmake --build

Then we need to define a build script so that the cmake project is added to the mix

// build.rs
use cmake::Config;

fn main() {
    let dst = Config::new("libalsa").build();
    // linker directives, see the link above for more reference.
    println!("cargo:rustc-link-search=native={}", dst.display());
    println!("cargo:rustc-link-lib=static=alsa");
}

And of course you need to update Cargo.toml to specify the path of the build script, just add this under the package section

build = "build.rs"

Now actually back to Rust, where we’ll be using the C function we defined earlier in our unsafe Rust code (chills).

Make sure that the name and kind of library match the ones in build.rs, then add your function with types same as they were in C, e.g int => i32, double => f64 and so on…

// src/main.rs
#[link(name = "alsa", kind = "static")]
extern "C" {
    // clippy will scream this:
    // warning: `extern` block uses type `str`, which is not FFI-safe
    // well, I have a great come back, SHUT UP CLIPPY!
    fn greet_3(name: &'static str);
}

fn main() {
    unsafe {
        greet_3("Dingus");
    }
}

Compile and run and you shall see something like this

cargo run
# Hello Dingus, some number: 9.849155

That was fun wasn’t it? well the ALSA won’t be, cuz I wrote the C code like 2 years ago, so I don’t have much knowledge why and how it’s running…

Hello ALSA

Hello ALSA, I’d like number 3 please, which will be those C snippets

// libalsa/alsa.h
#ifndef ALSA_RS_H
#define ALSA_RS_H

#include <alsa/asoundlib.h>
#include <math.h>
#include <sys/types.h>

extern int init_alsa();
extern int destroy_alsa();
extern int play_frequency(float freq, u_int16_t rate, float latency,
                          float duration);

#endif

Seriously I’m really lazy to re-open the past, just take it as is, and there’s a little bit of math, aaah back in the day when I could do math.

// libalsa/alsa.c
#include "alsa.h"

snd_pcm_t *handle;

long long second_to_micro(float seconds) { return (long long)(seconds * 1e6); }

int init_alsa() {
  return snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK,
                      0 /* blocked mode */);
}

int destroy_alsa() { return snd_pcm_close(handle); }

int play_frequency(float freq, u_int16_t rate, float latency, float duration) {
  if (latency > 150.0) {
    latency = 150.0;
  }
  latency = second_to_micro(latency);

  unsigned char buffer[(int)(rate * duration)];

  for (int i = 0; i < sizeof(buffer); i++) {
    buffer[i] = 0xFF * sin(2 * M_PI * freq * i / rate);
  }

  if (0 != snd_pcm_set_params(handle, SND_PCM_FORMAT_U8,
                              SND_PCM_ACCESS_RW_INTERLEAVED, 1 /* channels */,
                              rate /* rate [Hz] */, 1 /* soft resample */,
                              latency /* latency [us] */)) {
    return 0;
  }

  snd_pcm_writei(handle, buffer, sizeof(buffer));

  return 0;
}

Back to Rust, update your build.rs to include a linker directive that will link ALSA into the project.

// build.rs
use cmake::Config;

fn main() {
    let dst = Config::new("libalsa").build();

    println!("cargo:rustc-link-search=native={}", dst.display());
    println!("cargo:rustc-link-lib=static=alsa");
    println!("cargo:rustc-link-lib=dylib=asound");
}

Then import the C functions using the extern keyword

// src/main.rs
#[link(name = "alsa", kind = "static")]
extern "C" {
    fn init_alsa() -> i32;
    fn destroy_alsa() -> i32;
    fn play_frequency(freq: f32, rate: u16, latency: f32, duration: f32) -> i32;
}

fn main() {
    unsafe {
        init_alsa();
        // play a little sensoring sound for 5 seconds.
        play_frequency(1000.0, 44100, 0.1, 5.0);
        destroy_alsa();
    }
}

Yes as you can see the functions return a i32 which is the int retuned form C that represented the status code, we’re gonna ignore them here :)

Rains of Castamere

Full example can be found here, but let’s write it here as well, actually we’ll just be adding more Rust stuff that’ll represent the first 29 notes of The Rains of Castamere.

Just a little heads up, I have no idea how music works, I did some research, and I got some terms, played a little around, and got the frequencies of the notes, I would’ve continued, but it doesn’t sound that good, so I stopped, if you can help with the notes, or the namings of the stuff, contact me, or a PR to this blog post.

Anyways, let’s define the NoteDuration enum, which will hold common multipliers of a beat or something.

enum NoteDuration {
    TwoNotes,
    WholeNote,
    HalfNote,
}

impl NoteDuration {
    fn value(&self, secs: f32) -> f32 {
        match *self {
            Self::TwoNotes => secs * 2.0,
            Self::WholeNote => secs,
            Self::HalfNote => secs * 0.5,
        }
    }
}

Then define a Note struct which will be hodling the frequency of the note, and its duration.

struct Note {
    freq: f32,
    duration: NoteDuration,
}

impl Note {
    fn new(freq: f32, duration: NoteDuration) -> Self {
        Self { freq, duration }
    }
}

Now let’s play the notes.

unsafe {
    vec![
        Note::new(110.0, NoteDuration::HalfNote),
        Note::new(174.61, NoteDuration::WholeNote),
        Note::new(110.0, NoteDuration::HalfNote),
        Note::new(164.81, NoteDuration::WholeNote),
        Note::new(110.0, NoteDuration::HalfNote),
        Note::new(174.61, NoteDuration::HalfNote),
        Note::new(196.0, NoteDuration::WholeNote),
        Note::new(164.81, NoteDuration::HalfNote),
        Note::new(110.0, NoteDuration::WholeNote),
        Note::new(196.0, NoteDuration::WholeNote),
        Note::new(174.61, NoteDuration::WholeNote),
        Note::new(164.81, NoteDuration::HalfNote),
        Note::new(146.83, NoteDuration::WholeNote),
        Note::new(164.81, NoteDuration::HalfNote),
        Note::new(130.81, NoteDuration::HalfNote),
        Note::new(220.0, NoteDuration::WholeNote),
        Note::new(130.81, NoteDuration::HalfNote),
        Note::new(196.0, NoteDuration::WholeNote),
        Note::new(130.81, NoteDuration::HalfNote),
        Note::new(220.0, NoteDuration::WholeNote),
        Note::new(233.08, NoteDuration::WholeNote),
        Note::new(196.0, NoteDuration::WholeNote),
        Note::new(220.0, NoteDuration::HalfNote),
        Note::new(233.08, NoteDuration::WholeNote),
        Note::new(220.0, NoteDuration::WholeNote),
        Note::new(196.0, NoteDuration::WholeNote),
        Note::new(174.61, NoteDuration::WholeNote),
        Note::new(174.61, NoteDuration::TwoNotes),
        Note::new(164.81, NoteDuration::WholeNote),
    ]
    .iter()
    .for_each(|note| {
        println!("{}", note.freq);
        play_frequency(note.freq, 44100, 0.1, note.duration.value(1.0));
        //                      sample, latency
        //                      rate
    });
}

Quote of the day

“And who are you, the proud lord said, That I must bow so low? Only a cat of a different coat, That’s all the truth I know. In a coat of gold or a coat of red, A lion still has claws, And mine are long and sharp, my lord, As long and sharp as yours. And so he spoke, and so he spoke, That lord of Castamere, But now the rains weep o’er his hall, With no one there to hear. Yes now the rains weep o’er his hall, And not a soul to hear.”
- George R.R. Martin