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.
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.
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.)
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 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. |
"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
Simplest functions. Visibility. Return types. Parameter types. Mutable pointers to values; &mut x as *mut T; Strings.
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.
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.
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.
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.
-
[] 'Doc attributes'. https://github.com/mozilla/rust/wiki/Doc-attributes