Better_Software_Header_Mobile Better_Software_Header_Web

Find what you need - explore our website and developer resources

Weighing up Zngur and CXX for Rust/C++ Interop

// Exposes nested vector types
mod vec {
    type Vec<i32> {
        #layout(size = 24, align = 8);
        wellknown_traits(Debug);
        fn new() -> Vec<i32>;
        fn push(&mut self, i32);
        fn len(&self) -> usize;
    }

    type Vec<Vec<i32>> {
        #layout(size = 24, align = 8);
        wellknown_traits(Debug);
        fn new() -> Vec<Vec<i32>>;
        fn push(&mut self, Vec<i32>);
        fn len(&self) -> usize;
    }
}
auto row1 = to_vec({1,0,0});
auto row2 = to_vec({0,2,0});
auto row3 = to_vec({0,0,1});

auto matrix1 = Vec<Vec<int32_t>>::new_();
auto matrices = Vec<Vec<Vec<int32_t>>>::new_();

matrix1.push(std::move(row1));
matrix1.push(std::move(row2));
matrix1.push(std::move(row3));

matrices.push(std::move(matrix1));

zngur_dbg(rust::crate::determinants(matrices));
/// Treating nested vector like a 3x3 matrix, returns list of 
pub fn determinants(matrices: &Vec<Vec<Vec<i32>>>) -> Vec<i32> {
    let mut result = vec![];
    for m in matrices {
        // Naming components to make formula easier to read
        let a = m[0][0];
        // ...
        let i = m[2][2];

        let det = a*e*i + b*f*g + c*d*h - (c*e*g + a*f*h + b*d*i);
        result.push(det);
    }

    result
}
// We define a newtype, and can give it some basic methods
#[derive(Debug)]
struct Matrix {
    inner: Vec<Vec<i32>>,
}

impl Matrix {
    fn get_row(&self, row: usize) -> &Vec<i32> {
        &self.inner[row]
    }
}
// We use an app class to hold our methods
ExampleApp::ExampleApp() {}

std::unique_ptr<ExampleApp> new_app() {
  return std::unique_ptr<ExampleApp>(new ExampleApp());
}

// Calculating the determinants the same as the Zngur example
int32_t ExampleApp::calculate_determinant(const Matrix &m) const {
  auto row0 = m.get_row(0);
  auto row1 = m.get_row(1);
  auto row2 = m.get_row(2);
  
  // We can use the index operator, because CXX maps Rust Vec to C++ vector, something not possible in Zngur
  auto a = row0[0];
  // ...
  auto i = row2[2];

  auto det = a*e*i + b*f*g + c*d*h - (c*e*g + a*f*h + b*d*i);
  return det;
}
#[cxx::bridge]
mod ffi {
    extern "Rust" {
        type Matrix;
        fn get_row(self: &Matrix, row: usize) -> &Vec<i32>;
    }

    unsafe extern "C++" {
        include!("cxx-example/include/example.h");
        type ExampleApp;

        fn new_app() -> UniquePtr<ExampleApp>;
        fn calculate_determinant(&self, matrix: &Matrix) -> i32;
    }
}
fn main() {
    let app = ffi::new_app();
    let m = vec![vec![1, 2, 3], vec![4, 5, 0], vec![0, 0, 9]];
    let mat = Matrix { inner: m };
    let det = app.calculate_determinant(&mat);
}
trait MyTrait {
    // Your trait methods here
    fn as_my_trait(self) -> Box<dyn MyTrait> where Self: Sized + 'static { 
        Box::new(self)
    }
}
mod crate {
    // Declaring the methods we want to use in the trait
    trait MediaType {
        fn author(&self) -> ::std::string::String;
        fn title(&self) -> ::std::string::String;
        fn rating(&self) -> u8;
        fn media_type(&self) -> ::std::string::String;
    }

    // Box type must be declared too
    type Box<dyn crate::MediaType> {
        #layout(size = 16, align = 8);
    }

    // A rust function to display Movies, Songs, Albums, and any new media types
    // Simply prints out the author, title and rating in a pretty format
    fn display_media(Box<dyn MediaType>);

    type Movie {
        #layout(size = 56, align = 8);
        wellknown_traits(Debug);

        constructor {author_name: ::std::string::String, movie_title: ::std::string::String, movie_rating: u8};
        fn as_boxed(self) -> Box<dyn MediaType> use MediaType; // The use keyword specifies this method comes from the trait

        field author_name (offset = 0, type = ::std::string::String);
        field movie_title (offset = 24, type = ::std::string::String);
        field movie_rating (offset = 48, type = u8);
    }

    type Song {
        #layout(size = 56, align = 8);
        wellknown_traits(Debug);

        constructor {author: ::std::string::String, title: ::std::string::String, rating: u8};

        field author (offset = 0, type = ::std::string::String);
        field title (offset = 24, type = ::std::string::String);
        field rating (offset = 48, type = u8);
    }
}

// This block declares that we are going to implement the trait using C++
// This is done using the Impl template provided by Zngur
extern "C++" {
    impl crate::MediaType for crate::Movie {
        fn author(&self) -> ::std::string::String;
        fn title(&self) -> ::std::string::String;
        fn rating(&self) -> u8;
        fn media_type(&self) -> ::std::string::String;
    }
}
// Trait impls
String
rust::Impl<Movie, MediaType>::author(
    rust::Ref<Movie> self) {
    // String have to be clone to be owned in C++ since they are allocated by the rust heap, so cannot be moved easily
  return self.author_name.clone();
}

String
rust::Impl<Movie, MediaType>::title(
    rust::Ref<Movie> self) {
  return self.movie_title.clone();
}

String
rust::Impl<Movie, MediaType>::media_type(
    rust::Ref<Movie> _self) {
  return "Cinema"_rs.to_owned();
}

uint8_t
rust::Impl<Movie, MediaType>::rating(
    rust::Ref<Movie> self) {
  return self.movie_rating;
}
// Implementing MediaType for rust::Song
class SongMedia : public MediaType {
  Song song;

public:
  SongMedia(Song s) : song(std::move(s)) {}

 ...
};

...

// Create an instance from a rust::Song
auto song_media = SongMedia(std::move(song));

// Call its methods
auto artist = song_media.author();
zngur_dbg(artist);

// Get a boxed trait object using the Zngur helper
auto boxed_song_media = Box<Dyn<MediaType>>::make_box<SongMedia>(std::move(song_media));
display_media(std::move(boxed_song_media));
// Using the rust as_boxed method as shown above (called as_boxed here)
auto my_album_as_media_type = my_album.as_boxed();
display_media(std::move(my_album_as_media_type));
// display_media will use the C++ implentations, but note as_boxed comes from the trait in Rust.
auto my_movie_as_media_type = jaws.as_boxed();
display_media(std::move(my_movie_as_media_type));
// Abstract base / interface
class Media {
    public:
        virtual rust::String media_type() const = 0;
        virtual rust::String author() const = 0;
        virtual rust::String title() const = 0;
        virtual int rating() const = 0;
        const Media& as_media() const { return *this; }
};
class Movie : public Media {
public:
    Movie(rust::String title, rust::String author, int rating) : m_title(std::move(title)), m_author(std::move(author)), m_rating(rating) {}
    rust::String author() const override {
        return m_author;
    }

    rust::String title() const override {
        return m_title;
    }

    rust::String media_type() const override {
        return rust::String("Movie");
    }

    int rating() const override {
        return m_rating;
    }

private:
    rust::String m_title;
    rust::String m_author;
    int m_rating;
};
std::unique_ptr<Movie> make_movie(rust::String title, rust::String author, int rating) {
    return std::make_unique<Movie>(title, author, rating);
}

std::unique_ptr<Song> make_song(rust::String title, rust::String author, int rating) {
    return std::make_unique<Song>(title, author, rating);
}

// function to print all media types in a readable manner
void display_media(const Media& m) {
    ...
}
#[cxx::bridge]
mod ffi {
    unsafe extern "C++" {
        include!("cxx-example/include/media.h");

        type Media;
        type Movie;
        type Song;

        fn make_movie(title: String, author: String, rating: i32) -> UniquePtr<Movie>;
        fn make_song(title: String, author: String, rating: i32) -> UniquePtr<Song>;

        fn as_media(self: &Movie) -> &Media;
        fn as_media(self: &Song) -> &Media;

        fn display_media(m: &Media);
    }
}
fn main() {
    // Media
    let jaws = make_movie("Jaws".to_owned(), "Steven Spielberg".to_owned(), 8);
    let fur_elise = make_song("Fur Elise".to_owned(), "Mozart".to_owned(), 5);

    display_media(jaws.as_ref().unwrap().as_media());
    display_media(fur_elise.as_ref().unwrap().as_media());
}
use zngur::Zngur;

fn main() {
    build::rerun_if_changed("main.zng");
    build::rerun_if_changed("prelude.zng");
    build::rerun_if_changed("trait_objects.zng");
    build::rerun_if_changed("nested_types.zng");
    build::rerun_if_changed("main.cpp");
    build::rerun_if_changed("nested_types.rs");
    build::rerun_if_changed("trait_objects.rs");

    let crate_dir = build::cargo_manifest_dir();

    // Generate glue including the header, implementations (generated.cpp) and Rust code.
    Zngur::from_zng_file(crate_dir.join("main.zng"))
        .with_cpp_file(crate_dir.join("generated.cpp"))
        .with_h_file(crate_dir.join("generated.h"))
        .with_rs_file(crate_dir.join("./src/generated.rs"))
        .generate();

    let my_build = &mut cc::Build::new();
    let my_build = my_build.cpp(true).std("c++17");
    let my_build = || my_build.clone();

    // Build the C++ code using the CC builder, including the entrypoint to the program, and the generated impls
    my_build().file("generated.cpp").compile("generated");
    my_build().file("main.cpp").compile("main");
}
fn main() {
    cxx_build::bridge("src/main.rs")
        .file("src/example.cc")
        .compile("cxx-example");

    println!("cargo:rerun-if-changed=src/example.cc");
    println!("cargo:rerun-if-changed=include/example.h");
    println!("cargo:rerun-if-changed=include/media.h");
}
FeatureCXXZngur
Generic SupportLimited to built in types (listed below) when trying to define new generic types, e.g. no support for HashMap or Vec<Vec<T>> without a newtype wrapper.Very broad support for defining Vec<Vec<T>>, HashMap<K, V>, Arc<dyn Trait>, with the caveat that each specialization needs to be defined manually.
Built In SupportCXX has built in support for Vec, String, UniquePtr, &[T] and a few others, and has strong guarantees about them.Very limited built in support, not even supporting String or bool out of the box, but has very broad support for specifying these types yourself.
Trait ObjectsNo direct support for Box<dyn Trait>, Docs recommend using UniquePtr insteadFull support: C++ types can implement Rust traits, and trait objects can be passed over the boundary.
Async SupportPartial via cxx-async; integrates with Rust Future including futures crate.None yet. Can only emulate async via passing boxed function pointers and via channels.
Thread SafetyEnforces Send and Sync requirements via static assertions.No assertions, but Mutex, Arc are supported.
Build SystemIntegrated via cxx_build crate to support Cargo, CMake, and Bazel builds. Also has the cxxbridge tool to generate glue code from a fileCLI tool to generate glue code (zngur g file.zng) then manual build. Build scripts are supported but less so, with lesser adoption.
Documentation StatusMature, comprehensive docs at cxx.rs.Sparse and incomplete, examples can be found in GitHub but they are limited.
AdoptionWidely used, including in large projects like Chromium and Android.Early adoption, Adobe seems to have taken an interest.
OverheadZero-overhead abstraction, no serialization or copying.Slight overhead possible; prioritizes flexibility and expressiveness.
Language DefinitionInline within #[cxx::bridge] macro in the main Rust file.Specified in an IDL file (.zng).
Best UseStable, performant FFI, integration with CXX-Qt, projects with simple types at the boundaryResearch, quick prototyping, or projects making strong use of Traits and dynamic dispatch in Rust. Easier to integrate into an existing C++ project

About KDAB

01_NoPhoto

Ben Ford

Software Engineer

Sign up for the KDAB Newsletter

Learn Rust

Learn more

Learn Modern C++

Learn more