cpp 0.1.0

Write C++ code inline in your rust code!
Documentation

rust-cpp

Build Status Build status

rust-cpp is a build tool & macro which enables you to write C++ code inline in your rust code.

NOTE: This crate works on stable rust, but it is not stable itself. You can use this version all you want, but don't be surprised when a 0.2 release is made which completely breaks backwords compatibility. I view this crate as more of an experiment than a product.

As the tools come into stable rust to make this more practical to use, I expect that it will stabilize. Namely, I do not expect that this module will have a stable-ish interface until we get a stable procedural macro system.

Setup

NOTE: As the stable branch of rust-cpp is not on crates.io, you will have to download it and use the path manually. This will likely be changed in the future.

Add cpp as a dependency to your project. It will need to be added both as a build dependency, and as a normal dependency, with different flags. You'll also need a build.rs set up for your project.

[package]
# ...
build = "build.rs"

[build-dependencies]
# ...
cpp = { version = "0.1.0", features = ["build"] }

[dependencies]
# ...
cpp = { version = "0.1.0", features = ["macro"] }

You'll also then need to call the cpp build plugin from your build.rs. It should look something like this:

extern crate cpp;

fn main() {
    cpp::build("src/lib.rs", "crate_name", |cfg| {
        // cfg is a gcc::Config object. You can use it to add additional
        // configuration options to the invocation of the C++ compiler.
    });
}

Usage

In your crate, include the cpp crate macros:

#[macro_use]
extern crate cpp;

Then, use the cpp! macro to define code and other logic which you want shared between rust and C++. The cpp! macro supports the following forms:

cpp! {
    // Include a C++ library into the C++ shim. Only the `#include` directive 
    // is supported in this context.
    #include <cstdlib>
    #include "foo.h"
    
    // Write some logic directly into the shim. Either a curly-braced block or
    // string literal are supported
    raw {
        #define X 10
        struct Foo {
            uint32_t x;
        };
    }
    
    raw r#"
        #define Y 20
    "#
    
    // Define a function which can be called from rust, but is implemented in
    // C++. Its name is used as the C function name, and cannot collide with
    // other C functions. The body may be defined as a curly-braced block or 
    // string literal.
    // These functions are unsafe, and can only be called from unsafe blocks.
    fn my_function(x: i32 as "int32_t", y: u64 as "uint32_t") -> f32 as "float" {
        return (float)(x + y);
    }
    fn my_raw_function(x: i32 as "int32_t") -> u32 as "uint32_t" r#"
        return x;
    "#
    
    // Define a struct which is shared between C++ and rust. In C++-land its
    // name will be in the global namespace. In rust it will be located 
    // wherever the cpp! block is located
    struct MyStruct {
        x: i32 as "int32_t",
        y: *const i8 as "const char*",
    }
    
    // Define an enum which is shared between C++ and rust. In C++-land it 
    // will be defined in the global namespace as an `enum`. In rust,
    // it will be located wherever the cpp! block is located.
    enum MyEnum {
        A, // Known in C++ as `A`
        B,
        C,
        D,
    }

    // Enums can also be declared as `enum class` or `enum prefix` which will
    // cause them to be defined in C++-land as either an `enum class` or an
    // enum with each of its members prefixed with `EnumName_`.
    enum class MyEnumClass {
        A, // Known in C++ as `MyEnumClass::A`
        B,
        C,
        D,
    }

    enum prefix MyEnumPrefix {
        A, // Known in C++ as `MyEnumPrefix_A`
        B,
        C,
        D,
    }
}

cpp also provides a header which may be useful for interop code. This header includes <cstdint>, which means that a sufficiently modern C++ compiler may be required to use it. This header, rust_types.h, can be included with:

cpp! {
    #include "rust_types.h"
}

It provides the rs namespace, which contains various type definitions for rust types, such as the numeric types (rs::i8, rs::u64, rs::f32, rs::usize, etc.), the rust slice type (&[u8] => rs::Slice<rs::u8>), and the rust trait object fat pointer type. There are also definitions for rs::bool_, which is guaranteed to be 1 byte wide, the size of a rust bool, and rs::char_ which is the size of a rust char. The full body of rust_types.h is included below.

#ifndef _RUST_TYPES_H_
#define _RUST_TYPES_H_

#include <cstdint>

namespace rs {
    typedef int8_t i8;
    static_assert(sizeof(i8) == 1, "int is the right size");
    typedef int16_t i16;
    static_assert(sizeof(i16) == 2, "int is the right size");
    typedef int32_t i32;
    static_assert(sizeof(i32) == 4, "int is the right size");
    typedef int64_t i64;
    static_assert(sizeof(i64) == 8, "int is the right size");
    typedef intptr_t isize;

    typedef uint8_t u8;
    static_assert(sizeof(u8) == 1, "int is the right size");
    typedef uint16_t u16;
    static_assert(sizeof(u16) == 2, "int is the right size");
    typedef uint32_t u32;
    static_assert(sizeof(u32) == 4, "int is the right size");
    typedef uint64_t u64;
    static_assert(sizeof(u64) == 8, "int is the right size");
    typedef uintptr_t usize;

    typedef float f32;
    static_assert(sizeof(f32) == 4, "float is the right size");
    typedef double f64;
    static_assert(sizeof(f64) == 8, "float is the right size");

    typedef u8 bool_;
    static_assert(sizeof(bool_) == 1, "booleans are the right size");

    typedef uint32_t char_;
    static_assert(sizeof(char_) == 4, "char is the right size");
}
#endif

Warning about Macros

rust-cpp cannot identify and parse the information found in cpp! blocks which are generated with macros. These blocks will correctly generate rust code, but will not generate the corresponding C++ code, most likely causing your build to fail with a linker error. Do not create cpp! {} blocks with macros to avoid this.