C/C++ Bindings
C/C++ bindings let your Rust library be used from C and C++ applications.
The template uses cbindgen to automatically generate header files, handling cross-language calling conventions and platform details so you can focus on writing Rust code.
Note
Generating C bindings makes it possible to then generate bindings for further languages, such as C#.
Many language binding tools work best with C-compatible interfaces.
Overview
- C Bindings: C-compatible headers generated by
cbindgen - C++ Bindings: C++ headers with namespaces and additional type safety
Both are generated automatically when the c-exports feature is enabled.
Example Bindings
C Header
#ifdef _MSC_VER
#define PACKED
#pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif
#ifndef prs_rs
#define prs_rs
/* Generated with cbindgen:0.29.0 */
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
/**
* Compresses the given data in `source`, placing it in `destimation`.
*
* Parameters
*
* - `src`: A pointer to the compressed data.
* - `src_len`: Length of the compressed data.
* - `destination`: A pointer to the decompressed data to be written.
*
* # Returns
*
* Number of bytes written to `destination`.
*
* # Safety
*
* It's safe as long as `dest` has sufficient length (max length: [`prs_calculate_max_compressed_size`])
* and the remaining parameters are valid.
*/
uintptr_t prs_compress(const unsigned char *src, unsigned char *dest, uintptr_t srcLen) ;
/**
* Decodes the maximum possible compressed size after compressing a file with provided
* `source_len` length.
*
* # Parameters
*
* - `source_len`: Length of the compressed data.
*
* # Returns
*
* The length of the decompressed data at `source`.
*
* # Remarks
*
* A properly compressed PRS file has a theoretical maximum size of 1.125 times the size of the
* original input. i.e. (1 byte for every 8 bytes of input).
*
* Up to 2 bytes may be added to that in addition, namely via:
* - Rounding file to next byte
* - Having to write 00 opcode after a compressed sequence of bytes to terminate.
*/
uintptr_t prs_calculate_max_compressed_size(uintptr_t sourceLen) ;
/**
* Decodes the compressed data at `source` without performing the actual decompression.
*
* You can use this operation to determine the size of the data to decompress
* without actually decompressing the data to a buffer.
*
* # Parameters
*
* - `source`: A pointer to the compressed data.
*
* # Returns
*
* The length of the decompressed data at `source`.
*
* # Safety
*
* Function is safe as long as the pointer points to valid PRS compressed data with
* a terminator byte.
*/
uintptr_t prs_calculate_decompressed_size(const unsigned char *src) ;
/**
* Decompresses PRS compressed data, in an unsafe manner, without any error handling.
*
* # Parameters
*
* - `source`: A pointer to the compressed data.
* - `destination`: A pointer to the decompressed data.
*
* # Returns
*
* - The length of the decompressed data.
*
* # Remarks
*
* The length of the decompressed data at `destination` should be sufficient to store the decompressed data.
*
* If you know the length of the compressed data (i.e. amount of bytes until end of compressed data),
* call [`prs_calculate_max_compressed_size`] to get the length of the decompressed data
* buffer.
*
* If you are unsure of the length, you use the [`prs_calculate_decompressed_size`]
* function to determine the length of the decompressed data (at expense of some additional overhead).
*
* # Safety
*
* Function is safe as long as the source points to valid PRS compressed data with
* a terminator byte. The destination should be large enough to store the decompressed data.
*/
uintptr_t prs_decompress(const unsigned char *src, unsigned char *dest) ;
#endif /* prs_rs */
C++ Header
#ifdef _MSC_VER
#define PACKED
#pragma pack(push, 1)
#else
#define PACKED __attribute__((packed))
#endif
#ifndef prs_rs
#define prs_rs
/* Generated with cbindgen:0.29.0 */
/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
#include <cstdarg>
#include <cstdint>
#include <cstdlib>
#include <ostream>
#include <new>
namespace prs_rs {
extern "C" {
/// Compresses the given data in `source`, placing it in `destimation`.
///
/// Parameters
///
/// - `src`: A pointer to the compressed data.
/// - `src_len`: Length of the compressed data.
/// - `destination`: A pointer to the decompressed data to be written.
///
/// # Returns
///
/// Number of bytes written to `destination`.
///
/// # Safety
///
/// It's safe as long as `dest` has sufficient length (max length: [`prs_calculate_max_compressed_size`])
/// and the remaining parameters are valid.
uintptr_t prs_compress(const unsigned char *src, unsigned char *dest, uintptr_t srcLen) ;
/// Decodes the maximum possible compressed size after compressing a file with provided
/// `source_len` length.
///
/// # Parameters
///
/// - `source_len`: Length of the compressed data.
///
/// # Returns
///
/// The length of the decompressed data at `source`.
///
/// # Remarks
///
/// A properly compressed PRS file has a theoretical maximum size of 1.125 times the size of the
/// original input. i.e. (1 byte for every 8 bytes of input).
///
/// Up to 2 bytes may be added to that in addition, namely via:
/// - Rounding file to next byte
/// - Having to write 00 opcode after a compressed sequence of bytes to terminate.
uintptr_t prs_calculate_max_compressed_size(uintptr_t sourceLen) ;
/// Decodes the compressed data at `source` without performing the actual decompression.
///
/// You can use this operation to determine the size of the data to decompress
/// without actually decompressing the data to a buffer.
///
/// # Parameters
///
/// - `source`: A pointer to the compressed data.
///
/// # Returns
///
/// The length of the decompressed data at `source`.
///
/// # Safety
///
/// Function is safe as long as the pointer points to valid PRS compressed data with
/// a terminator byte.
uintptr_t prs_calculate_decompressed_size(const unsigned char *src) ;
/// Decompresses PRS compressed data, in an unsafe manner, without any error handling.
///
/// # Parameters
///
/// - `source`: A pointer to the compressed data.
/// - `destination`: A pointer to the decompressed data.
///
/// # Returns
///
/// - The length of the decompressed data.
///
/// # Remarks
///
/// The length of the decompressed data at `destination` should be sufficient to store the decompressed data.
///
/// If you know the length of the compressed data (i.e. amount of bytes until end of compressed data),
/// call [`prs_calculate_max_compressed_size`] to get the length of the decompressed data
/// buffer.
///
/// If you are unsure of the length, you use the [`prs_calculate_decompressed_size`]
/// function to determine the length of the decompressed data (at expense of some additional overhead).
///
/// # Safety
///
/// Function is safe as long as the source points to valid PRS compressed data with
/// a terminator byte. The destination should be large enough to store the decompressed data.
uintptr_t prs_decompress(const unsigned char *src, unsigned char *dest) ;
} // extern "C"
} // namespace prs_rs
#endif // prs_rs
The C++ version adds namespaces and uses C++-style includes for better type safety.
How to Export Functions
Info
These examples demonstrate good practices for exporting Rust functions to C.
Use these patterns when writing your own export functions.
Basic Function Exports
Use #[no_mangle] and extern "C" to export functions:
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
a + b
}
Error Handling with Result Types
When you have a Rust function that returns Result<T, E>, use this pattern to handle errors safely in C.
use alloc::ffi::CString;
use core::ffi::c_char;
use core::ptr::null;
/// Result type for C exports
#[repr(C)]
pub struct OperationResult {
pub is_ok: bool,
pub ok: u32,
pub err: *const c_char,
}
#[no_mangle]
pub extern "C" fn perform_operation(value: u32) -> OperationResult {
match your_rust_function(value) {
Ok(result) => OperationResult {
is_ok: true,
ok: result,
err: null(),
},
Err(err) => OperationResult {
is_ok: false,
ok: 0,
err: CString::new(err.to_string()).unwrap().into_raw(),
},
}
}
Handling Optional Values
When you have a Rust function that returns Option<T>, use this pattern to handle optional values in C.
/// Optional value wrapper for C
#[repr(C)]
pub struct OptionalInt {
pub has_value: bool,
pub value: i32,
}
#[no_mangle]
pub extern "C" fn get_optional_value() -> OptionalInt {
match your_optional_function() {
Some(value) => OptionalInt {
has_value: true,
value,
},
None => OptionalInt {
has_value: false,
value: 0,
},
}
}
Memory Management
Always provide free functions for allocated memory:
#[no_mangle]
pub extern "C" fn free_string(s: *mut c_char) {
unsafe {
if !s.is_null() {
let _ = CString::from_raw(s);
}
}
}
#[no_mangle]
pub extern "C" fn free_operation_result(result: OperationResult) {
if !result.is_ok {
free_string(result.err as *mut c_char);
}
}
The user will be required to call these to dispose of the result. Here's a usage example:
// This allocates result
OperationResult result = perform_operation(42);
if (result.is_ok) {
// do something with valid result
} else {
printf("Error: %s\n", result.err);
}
// result must always be freed
free_operation_result(result);
Alternative Error Handling Approach
Info
For a more efficient approach, more similar to classic C programming, use negative values as errors with constants for readability.
// Alternative approach using constants for error codes
#[repr(C)]
pub struct OperationResultAlt {
pub value: i32, // Negative values indicate errors
}
// Constants for error codes
pub const OPERATION_SUCCESS: i32 = 0;
pub const OPERATION_ERROR_INVALID_INPUT: i32 = -1;
pub const OPERATION_ERROR_NETWORK: i32 = -2;
pub const OPERATION_ERROR_UNKNOWN: i32 = -3;
#[no_mangle]
pub extern "C" fn perform_operation_alt(value: i32) -> OperationResultAlt {
match your_rust_function(value) {
Ok(result) => OperationResultAlt {
value: result
},
Err(_) => OperationResultAlt {
value: OPERATION_ERROR_UNKNOWN
},
}
}
#[no_mangle]
pub extern "C" fn perform_operation_is_error(result: OperationResultAlt) -> bool {
result.value < OPERATION_SUCCESS
}
Manual Header Generation
Manual generation is only needed when adjusting configuration.
Headers are auto-generated in automated builds and published in releases.
The template uses cbindgen to generate C and C++ header files from your Rust code.
Using VSCode, press Ctrl+Shift+P → "Run Task" → Select one of:
- Generate C Bindings - Generate C headers only
- Generate C++ Bindings - Generate C++ headers only
Or from command line:
cd src
# Install cbindgen (one-time setup)
cargo install cbindgen
# Generate C bindings
cbindgen --config ../.github/cbindgen_c.toml --output bindings/c/your-project.h your-project
# Generate C++ bindings
cbindgen --config ../.github/cbindgen_cpp.toml --output bindings/cpp/your-project.hpp your-project
Replace your-project with your actual project name. Configuration files are located in .github/:
.github/cbindgen_c.toml- C bindings configuration.github/cbindgen_cpp.toml- C++ bindings configuration
Automatic Header Generation
Info
Your C/C++ headers are automatically generated when you push a tag to make a release.
When you push a release tag, headers are generated with cbindgen, and attached to your GitHub release. See Automated Testing & Publishing for details.
Generate headers locally:
cd src
cargo build --features c-exports # Generate C/C++ headers
Headers are placed in target/headers/ directory.

Headers in GitHub Releases
You can also find the headers in 'Artifacts' of 'GitHub Actions' runs for regular builds.
Integration with Existing Projects
Info
Add C/C++ bindings to existing projects by copying configuration files from the template.
1. Generate Template Files
First, generate a fresh template to get the latest configuration files:
cargo generate --git https://github.com/Reloaded-Project/reloaded-templates-rust.git
2. Copy Configuration Files
Copy these key files from the generated template to your existing project:
cbindgen_c.tomlandcbindgen_cpp.toml- cbindgen configurationsrc/exports.rs- template for your export functions- Relevant sections from
Cargo.tomlfor thec-exportsfeature
3. Update Project Configuration
Add the c-exports feature to your Cargo.toml:
[features]
default = ["std"]
std = []
c-exports = []
Include the exports module in your src/lib.rs:
#[cfg(feature = "c-exports")]
pub mod exports;
See the main documentation for more details.