Rapid prototyping C applications with Rust

Nov 13, 2016
4 minute read

Cargo is a gorgeous tool when it comes to building, packaging and shipping your
own Rust applications. Some of you guys are living in a C/C++ world where developing Rust applications is just a hobby.
A reason could be that Rust is currently not that highly accepted in the world of applications in production. This is
sad, but even within your day to day development it could be possible to use Rust for a lots of tools or prototyping
scenarios!

One example could be to use the possibilities of Rust and Cargo to do prototyping of C based applications. So how could
we do that?

This program does not do really special at all, but as a demonstration we print out all available command line
arguments. The interesting part happens within the Rust entry function in main.rs:

externcratelibc;usestd::env;usestd::ffi::CString;uselibc::{c_int,c_char};extern"C"{fnmain_c(argc:c_int,argv:*const*constc_char)->c_int;}fnmain(){// Get the current args and map them to a vector of zero// terminated c strings:letargs:Vec<CString>=env::args().filter_map(|arg|{CString::new(arg).ok()}).collect();// Convert these c strings to raw pointersletc_args:Vec<*constc_char>=args.iter().map(|arg|{arg.as_ptr()}).collect();// Call the main function within the created c libraryunsafe{main_c(c_args.len()asc_int,c_args.as_ptr());};}

The Foreign Function Interface (FFI) of Rust is pretty straight forward and Cargo even supports us in writing those
applications. We simply need to declare our C function within the scope of extern "C" { ... }. Since we cannot use the
basic C types within Rust we have to use the ones of the libc crate (like c_int or c_char). The main function
creates a Vector of C pointers to our command line arguments and passes them within the unsafe {...} block to our C
entry function.

Do we forgot something? Yes, what about the build.rs script? How can we tell Cargo to first build our C library
and then link it to the Rust application? The build script looks like:

Great, everything works as expected, …but stop! What happened here? We did not tell Cargo to link against a library
called libmain.a. How should Cargo know what to do with the library? That is one of the very important features of
such a great build environment: Strong defaults will lead into a better world. Cargo will simply assume that you call
your library libmain.a for the file main.c.

If we want to link another library to our C application we can simply use the link directive:

It is pretty brilliant: On one hand we have the full power of Rust and Cargo via the language itself within the
build.rs script. This means we have the full flexibility like in CMake by the usage of the complete programming
language set of Rust. On the other hand the strong defaults of Cargo will make it very easy to build release versions,
debug our code via gdb or lldb, package the application and write benchmarks or unit tests for your C functions.
This is all possible without the need of a cruel Makefile or a bloated CMake environment. How great is that?! 😀

Currently I am working on some Cargo port called ‘Craft’ which should combine
both worlds out of the box. Replacing the compiler facade is a lots of work and Cargo is even evolving very fast which
makes me think about writing just an cargo extension to do faster prototyping in C. What do you think?

The full source code of the small example project is available on GitHub.