How to export C++ struct fields to JSON using Rust & clang
Also see the Rust version of this post: How to export C++ struct fields to JSON using Rust & clang
In our previous post How to export C++ struct fields to JSON using clang and boost::json we discussed how to use libclang
in combination with boost::json
to parse a C++ source file and export struct definitions to JSON.
In this post we will achieve the same goal using Rust and the clang
crate.
main.rs
use clang::{Clang, Entity, EntityKind, Index};
use serde_json::{json, Value};
use std::env;
use std::fs;
use std::path::Path;
fn extract_struct_fields(entity: &Entity) -> Option<Value> {
if entity.get_kind() != EntityKind::StructDecl {
return None;
}
// Struct name (may be None for anonymous structs)
let struct_name = entity.get_name().unwrap_or_default();
// Collect fields with running index
let mut index = 0usize;
let mut fields = vec![];
entity.visit_children(|child, _parent| {
if child.get_kind() == EntityKind::FieldDecl {
let field_name = child.get_name().unwrap_or_default();
let field_type = child
.get_type()
.map(|t| t.get_display_name())
.unwrap_or_default();
fields.push(json!({
"index": index,
"name": field_name,
"type": field_type,
}));
index += 1;
}
clang::EntityVisitResult::Continue
});
Some(json!({
"name": struct_name,
"fields": fields
}))
}
fn main() {
// --- Argument & file checks (parity with the C++ version) ---
let mut args = env::args();
let exe = args.next().unwrap_or_else(|| "program".into());
let Some(filename) = args.next() else {
eprintln!("Usage: {} <source file>", exe);
std::process::exit(1);
};
let path = Path::new(&filename);
if !path.exists() {
eprintln!("File does not exist: {}", filename);
std::process::exit(1);
}
let code = match fs::read_to_string(&path) {
Ok(s) => s,
Err(_) => {
eprintln!("Error opening file: {}", filename);
std::process::exit(1);
}
};
if code.is_empty() {
eprintln!("File is empty: {}", filename);
std::process::exit(1);
}
// --- libclang setup & parse as an unsaved file named "test.cpp" ---
let clang = Clang::new().expect("Failed to load libclang");
let index = Index::new(&clang, /*exclude_decls_from_pch=*/ false, /*display_diagnostics=*/ false);
let unsaved = clang::Unsaved::new("test.cpp", &code);
// No special arguments needed (parity with CXTranslationUnit_None)
let tu = match index.parser("test.cpp").unsaved(&[unsaved]).parse() {
Ok(tu) => tu,
Err(_) => {
eprintln!("Failed to parse translation unit.");
std::process::exit(1);
}
};
// --- Walk the translation unit and gather structs ---
let root = tu.get_entity();
let mut structs = vec![];
root.visit_children(|child, _parent| {
if let Some(struct_json) = extract_struct_fields(&child) {
structs.push(struct_json);
}
clang::EntityVisitResult::Continue
});
// --- Output JSON identical in shape to the C++ version ---
let result = json!({ "structs": structs });
println!("{}", serde_json::to_string(&result).unwrap());
}
Cargo.toml
[package]
name = "rust_clang_structs"
version = "0.1.0"
edition = "2021"
[dependencies]
clang = "2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow