Skip to content

Instantly share code, notes, and snippets.

@Skrylar
Last active January 1, 2016 10:59
Show Gist options
  • Save Skrylar/8134638 to your computer and use it in GitHub Desktop.
Save Skrylar/8134638 to your computer and use it in GitHub Desktop.

Binding Rust to C

This document is a work in progress, and is primarily concerned with getting Rust code able to talk with C code. What we cover here is writing 'bindings', we will cover 'wrapping' up the bindings and providing an elegant interface in the next document.

Extern blocks

The most basic building block of interfacing Rust with C is the extern block, which is necessary for telling Rust that the C functions you wish to use actually exist.

Example.

Linkage

Just like spoken languages have different ways of communicating the same information, computer languages have 'linkage'. A function’s 'linkage' determines how the Rust code will call out to the C code. The most important you need to know right now are System, C and stdcall.

Note
Rust supports a wide variety of 'linkage' and 'ABIs', however these have been omitted as they are not important yet in this document.

Fortunately, specifying the 'linkage' for the whole extern block is easy: you simply place it within quotes after the extern keyword:

Example.

Now, every function that we define will be treated as though it had C 'linkage', which is the way every C function is exported in a Unix environment.

On some platforms such as Microsoft Windows, some libraries will not actually use C linkage. They will use one known as 'stdcall', and an obvious sign of this are 'undefined reference' errors which look like this: SDL_DestroyWindow@4. If you are dealing with these errors, try changing the linkage to stdcall and re-compiling your code.

Example.

What about if you are working on bindings for a library which exists on both Windows and Unix-like systems? Fortunately, Rust has a solution: System linkage. System instructs Rust to use the native way of accessing C functions that is specific to whichever platform you are currently building on.

Example.

This means that on Windows, you will get stdcall (the one with the weird at signs and numbers) and on Unix-like systems you will get C (the one starting with underscores.)

Attributes

In C you may be accustomed to using 'linker flags' whenever you bring a library in to your project. As a recap, whenever you compile a plain C file you may need to run this command to compile it:

Example.

If you decide to bring in the Simple Direct-Media Layer library for a project, you have to adjust your compile lines to account for that:

Example.

In Rust, there is no need for adding 'linker flags' at the compile line. You simply put an 'attribute' on the extern block, which tells the compiler you need a library to be compiled along with the program and it automatically takes care of it for you.

Example.

Note

If your libraries are in non-standard locations, you may run in to trouble since the linking process cannot find them. As linker flags are not passed on the compile line to rustc, you will need to set the LIBRARY_PATH environment appropriately. For example on a Linux shell, you can tell rustc to look for libraries installed to /home/user/local as follows:

LIBRARY_PATH=/home/user/local rustc source.rs

GNU Makefiles, Ruby Rake, and other build systems have their own way of setting environment variables. Consult the manual for your build system of choice if necessary.

Platform-specific Attributes

"My library is Windows-only!"

No problem! Rust provides a cfg attribute[DocAttr] which allows you to perform 'conditional compilation.' Using cfg you can easily tell Rust to only include a library on a specific platform. Simply prefix #[cfg(target_os="os name here"] before the extern block like so:

Example.

"That’s nice, but I want to link FooWin on Windows and Foo on Linux. Do I have to copy the whole extern block just to change the library name?"

Of course not. Thanks to using System linkage like we talked about earlier, the calling convention will use the preferred linkage for whichever OS you are using. To link the libraries, we can make two 'empty' extern blocks. These blocks will have a cfg attribute set so they are only considered for a specific platform, and a second link attribute tells Rust what libraries to link.

Example of windows and linux.

Now your crate will use the correct dependencies on all supported OS', and there is no need for the user of your code to fiddle with their compiler flags.

What 'target_os' values are supported? This is bound to expand as Rust grows in use, but the most common ones right now are:

  • linux

  • win32

  • macos

Chapter Review

Example library binding using all of what we know from above.

std::libc Types

Functions

Simplest functions. Visibility. Return types. Parameter types. Mutable pointers to values; &mut x as *mut T; Strings.

Enums & Constants

Rust enums are not the same thing as C enums. Option: Bind C enums as statics. Option: Write a converter which reads a C enum and returns a Rust enum, also do it in reverse. Advanced: Use a macro to avoid boilerplate on larger enums, have both conversion methods produced for us.

Globals

Structures

Write a structure in Rust which mirrors the structure in C. Proper interop involving handing these structs over to C. Proper interop borrowing structures owned by a C library. Hint on supporting garbage collection / reference counting in the wrapper layer.

Bit Masks

What to do if you see bit fields in a C structure? Dealing with bit masks and bit fields, which many C libs use for options.

Unions

Rust has no native type which is analgous to C unions. You need to find out the size of the union type. Create a shim type with a vector [u8, ..length] for storing the union when you need to. Use transmute to convert pointers to different types, and access the contents of the union. Show an example of passing a union to SDL and retrieving information out of it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment