C++

To run the cpp-example from here you need boost, cmake and a C++11 compatible compiler.

Project Structure

The projects consists of two parts: cpp-part and rust-part. The Rust part is compiled as shared library and linked into executable described by CMakeLists.txt. The cpp-part is the main part so its build system (cmake) invokes cargo to build the Rust part.

Building

It is a normal CMake project, so you can build it as an ordinary CMake project. By default it requires C++11 and boost, but if your compiler is modern enough you can use C++17 and then you don't need boost at all.

Just delete all mentions of boost here:

// build.rs
    let cpp_cfg = CppConfig::new(
        Path::new("..").join("cpp-part").join("rust-api"),
        "rust".into(),
    )
    .cpp_optional(CppOptional::Boost)
    .cpp_variant(CppVariant::Boost)
    .cpp_str_view(CppStrView::Boost);

The main functionality

This project demonstrates how to export Rust in the form of a class to C++.

Rust code:

// src/lib.rs
pub struct Foo {
    data: i32,
}

impl Foo {
    fn new(val: i32) -> Foo {
        Foo { data: val }
    }

    fn f(&self, a: i32, b: i32) -> i32 {
        self.data + a + b
    }

    fn set_field(&mut self, v: i32) {
        self.data = v;
    }
}

Described as class:

// src/cpp_glue.rs.in
foreign_class!(class Foo {
    self_type Foo;
    constructor Foo::new(_: i32) -> Foo;
    fn Foo::set_field(&mut self, _: i32);
    fn Foo::f(&self, _: i32, _: i32) -> i32;
    fn f2(_: i32) -> i32;
});

Usage from C++:

// main.cpp
    Foo foo(5);
    int res = foo.f(1, 2);

Borrowed foreign classes

For a regular foreign_class!, Rust &Foo arguments and return values use FooRef by value in C++. A Foo or const Foo& converts implicitly to FooRef, so either an owning object or an existing borrowed view can be passed to a generated method. For example, the C++ test fixture uses TestReferences and Foo:

TestReferences source{1, "source"};
TestReferences target{2, "target"};
FooRef view = source.get_foo_ref();
target.update_foo(view);
const Foo owned{3, "owned"};
target.update_foo(owned);

Copying a FooRef copies only its pointer. Keep the Rust object alive while using the view. Rust &mut Foo arguments still take Foo& in C++; a returned &mut Foo currently becomes a read-only FooRef.

A plain C++ class

By default, flapigen generates two C++ variants for a foreign_class!: an owning class and a non-owning reference class. If you don't want to deal with two variants and the need to convert one into the other in various cases, #[derive(PlainClass)] generates just one C++ class. By default, returning a Rust reference to that type (&ScoreAdjustment or &mut ScoreAdjustment) gives a compile error because there is no C++ reference wrapper. Return an owned value instead, or define an explicit outgoing foreign_typemap! for a custom borrowed C++ view. The C++ caller must keep the Rust referent alive while using such a view. The single class can also be forward-declared as class ScoreAdjustment; in a handwritten C++ header.

The Rust type and its binding are:

pub struct ScoreAdjustment {
    bonus: i32,
}

impl ScoreAdjustment {
    fn new(bonus: i32) -> Self {
        Self { bonus }
    }

    fn apply(&self, base: i32) -> i32 {
        base + self.bonus
    }
}
foreign_class!(
    #[derive(PlainClass)]
    class ScoreAdjustment {
        self_type ScoreAdjustment;
        constructor ScoreAdjustment::new(bonus: i32) -> ScoreAdjustment;
        fn ScoreAdjustment::apply(&self, base: i32) -> i32;
    }
);

The C++ helper header can now declare a function using the generated class:

namespace rust { class ScoreAdjustment; }

int adjusted_score(const rust::ScoreAdjustment& adjustment, int base);

The C++ implementation includes ScoreAdjustment.hpp before calling its method:

int adjusted_score(const ScoreAdjustment& adjustment, int base)
{
    return adjustment.apply(base);
}

The executable uses the helper like this:

    ScoreAdjustment adjustment(3);
    if (adjusted_score(adjustment, 5) != 8) {
        return EXIT_FAILURE;
    }