From a3fc9a3a84e8fdf7f89fb9abae35b66a1784896d Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sat, 5 Sep 2026 22:12:14 +0100 Subject: [PATCH 1/6] Translate in-class initializers --- cpp2rust/converter/converter.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index b1a1ae09..ea9a0d96 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -641,6 +641,10 @@ bool Converter::RecordDerivesDefault(const clang::RecordDecl *decl) { } for (auto f : decl->fields()) { + if (f->hasInClassInitializer()) { + return false; + } + // Records that contain function pointer do not derive Default if (auto ptr_ty = f->getType()->getAs()) { if (ptr_ty->getPointeeType()->isFunctionType()) { @@ -4019,8 +4023,13 @@ void Converter::EmitDefaultStructLiteral(const clang::RecordDecl *decl) { StrCat(GetRecordName(decl)); PushBrace brace(*this); for (auto *field : decl->fields()) { - StrCat(GetNamedDeclAsString(field), token::kColon, - GetDefaultAsString(field->getType()), token::kComma); + StrCat(GetNamedDeclAsString(field), token::kColon); + if (field->hasInClassInitializer()) { + ConvertVarInit(field->getType(), field->getInClassInitializer()); + } else { + StrCat(GetDefaultAsString(field->getType())); + } + StrCat(token::kComma); } } From 0f9ed7ac2c6871e0b985437c2a7c38e8b1d5b931 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 6 Sep 2026 11:01:02 +0100 Subject: [PATCH 2/6] Add in class initializer test --- tests/unit/in_class_field_initializer.cpp | 22 ++++ .../out/refcount/array_of_noncopy_struct.rs | 10 +- .../refcount/in_class_field_initializer.rs | 108 ++++++++++++++++++ .../out/unsafe/array_of_noncopy_struct.rs | 10 +- .../out/unsafe/in_class_field_initializer.rs | 52 +++++++++ 5 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 tests/unit/in_class_field_initializer.cpp create mode 100644 tests/unit/out/refcount/in_class_field_initializer.rs create mode 100644 tests/unit/out/unsafe/in_class_field_initializer.rs diff --git a/tests/unit/in_class_field_initializer.cpp b/tests/unit/in_class_field_initializer.cpp new file mode 100644 index 00000000..8d6d6e56 --- /dev/null +++ b/tests/unit/in_class_field_initializer.cpp @@ -0,0 +1,22 @@ +#include +#include + +struct Inner { + int x = 3; + int y = 4; +}; + +struct S { + int a = 1; + char b = 2; + Inner c = {}; +}; + +int main() { + S s{}; + assert(s.a == 1); + assert(s.b == 2); + assert(s.c.x == 3); + assert(s.c.y == 4); + return 0; +}; diff --git a/tests/unit/out/refcount/array_of_noncopy_struct.rs b/tests/unit/out/refcount/array_of_noncopy_struct.rs index bfd0462f..58358533 100644 --- a/tests/unit/out/refcount/array_of_noncopy_struct.rs +++ b/tests/unit/out/refcount/array_of_noncopy_struct.rs @@ -6,7 +6,7 @@ use std::io::prelude::*; use std::io::{Read, Seek, Write}; use std::os::fd::AsFd; use std::rc::{Rc, Weak}; -#[derive(Default)] +#[derive()] pub struct NonCopy { pub data: Value>, pub tag: Value, @@ -20,6 +20,14 @@ impl Clone for NonCopy { this } } +impl Default for NonCopy { + fn default() -> Self { + NonCopy { + data: Rc::new(RefCell::new(Default::default())), + tag: Rc::new(RefCell::new(0)), + } + } +} impl ByteRepr for NonCopy { fn byte_size() -> usize { 32 diff --git a/tests/unit/out/refcount/in_class_field_initializer.rs b/tests/unit/out/refcount/in_class_field_initializer.rs new file mode 100644 index 00000000..9dd6ef2a --- /dev/null +++ b/tests/unit/out/refcount/in_class_field_initializer.rs @@ -0,0 +1,108 @@ +extern crate libcc2rs; +use libcc2rs::*; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::io::prelude::*; +use std::io::{Read, Seek, Write}; +use std::os::fd::AsFd; +use std::rc::{Rc, Weak}; +#[derive()] +pub struct Inner { + pub x: Value, + pub y: Value, +} +impl Clone for Inner { + fn clone(&self) -> Self { + let mut this = Self { + x: Rc::new(RefCell::new((*self.x.borrow()))), + y: Rc::new(RefCell::new((*self.y.borrow()))), + }; + this + } +} +impl Default for Inner { + fn default() -> Self { + Inner { + x: Rc::new(RefCell::new(3)), + y: Rc::new(RefCell::new(4)), + } + } +} +impl ByteRepr for Inner { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.x.borrow()).to_bytes(&mut buf[0..4]); + (*self.y.borrow()).to_bytes(&mut buf[4..8]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + x: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + y: Rc::new(RefCell::new(::from_bytes(&buf[4..8]))), + } + } +} +#[derive()] +pub struct S { + pub a: Value, + pub b: Value, + pub c: Value, +} +impl Clone for S { + fn clone(&self) -> Self { + let mut this = Self { + a: Rc::new(RefCell::new((*self.a.borrow()))), + b: Rc::new(RefCell::new((*self.b.borrow()))), + c: Rc::new(RefCell::new((*self.c.borrow()).clone())), + }; + this + } +} +impl Default for S { + fn default() -> Self { + S { + a: Rc::new(RefCell::new(1)), + b: Rc::new(RefCell::new(2_u8)), + c: Rc::new(RefCell::new(Inner { + x: Rc::new(RefCell::new(3)), + y: Rc::new(RefCell::new(4)), + })), + } + } +} +impl ByteRepr for S { + fn byte_size() -> usize { + 16 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.a.borrow()).to_bytes(&mut buf[0..4]); + (*self.b.borrow()).to_bytes(&mut buf[4..5]); + (*self.c.borrow()).to_bytes(&mut buf[8..16]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + a: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + b: Rc::new(RefCell::new(::from_bytes(&buf[4..5]))), + c: Rc::new(RefCell::new(::from_bytes(&buf[8..16]))), + } + } +} +pub fn main() { + std::process::exit(main_0()); +} +fn main_0() -> i32 { + let s: Value = Rc::new(RefCell::new(S { + a: Rc::new(RefCell::new(1)), + b: Rc::new(RefCell::new(2_u8)), + c: Rc::new(RefCell::new(Inner { + x: Rc::new(RefCell::new(3)), + y: Rc::new(RefCell::new(4)), + })), + })); + assert!(((*(*s.borrow()).a.borrow()) == 1)); + assert!((((*(*s.borrow()).b.borrow()) as i32) == 2)); + assert!(((*(*(*s.borrow()).c.borrow()).x.borrow()) == 3)); + assert!(((*(*(*s.borrow()).c.borrow()).y.borrow()) == 4)); + return 0; +} diff --git a/tests/unit/out/unsafe/array_of_noncopy_struct.rs b/tests/unit/out/unsafe/array_of_noncopy_struct.rs index 7fedfe8c..ba020323 100644 --- a/tests/unit/out/unsafe/array_of_noncopy_struct.rs +++ b/tests/unit/out/unsafe/array_of_noncopy_struct.rs @@ -7,11 +7,19 @@ use std::io::{Read, Seek, Write}; use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; use std::rc::Rc; #[repr(C)] -#[derive(Clone, Default)] +#[derive(Clone)] pub struct NonCopy { pub data: Vec, pub tag: i32, } +impl Default for NonCopy { + fn default() -> Self { + NonCopy { + data: Default::default(), + tag: 0, + } + } +} pub fn main() { unsafe { std::process::exit(main_0() as i32); diff --git a/tests/unit/out/unsafe/in_class_field_initializer.rs b/tests/unit/out/unsafe/in_class_field_initializer.rs new file mode 100644 index 00000000..81414d2c --- /dev/null +++ b/tests/unit/out/unsafe/in_class_field_initializer.rs @@ -0,0 +1,52 @@ +extern crate libc; +use libc::*; +extern crate libcc2rs; +use libcc2rs::*; +use std::collections::BTreeMap; +use std::io::{Read, Seek, Write}; +use std::os::fd::{AsFd, FromRawFd, IntoRawFd}; +use std::rc::Rc; +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Inner { + pub x: i32, + pub y: i32, +} +impl Default for Inner { + fn default() -> Self { + Inner { x: 3, y: 4 } + } +} +#[repr(C)] +#[derive(Copy, Clone)] +pub struct S { + pub a: i32, + pub b: libc::c_char, + pub c: Inner, +} +impl Default for S { + fn default() -> Self { + S { + a: 1, + b: (2 as libc::c_char), + c: Inner { x: 3, y: 4 }, + } + } +} +pub fn main() { + unsafe { + std::process::exit(main_0() as i32); + } +} +unsafe fn main_0() -> i32 { + let mut s: S = S { + a: 1, + b: (2 as libc::c_char), + c: Inner { x: 3, y: 4 }, + }; + assert!(((s.a) == (1))); + assert!(((s.b as i32) == (2))); + assert!(((s.c.x) == (3))); + assert!(((s.c.y) == (4))); + return 0; +} From 950e1469461ad6f36d101e9c31f0ba0bdc8e8637 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Sun, 6 Sep 2026 12:12:25 +0100 Subject: [PATCH 3/6] Add implicitely initialized Inner --- tests/unit/in_class_field_initializer.cpp | 5 ++++- .../out/refcount/in_class_field_initializer.rs | 18 +++++++++--------- .../out/unsafe/in_class_field_initializer.rs | 10 +++++----- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/tests/unit/in_class_field_initializer.cpp b/tests/unit/in_class_field_initializer.cpp index 8d6d6e56..71e83f3e 100644 --- a/tests/unit/in_class_field_initializer.cpp +++ b/tests/unit/in_class_field_initializer.cpp @@ -10,13 +10,16 @@ struct S { int a = 1; char b = 2; Inner c = {}; + Inner d; }; int main() { - S s{}; + S s; assert(s.a == 1); assert(s.b == 2); assert(s.c.x == 3); assert(s.c.y == 4); + assert(s.d.x == 3); + assert(s.d.y == 4); return 0; }; diff --git a/tests/unit/out/refcount/in_class_field_initializer.rs b/tests/unit/out/refcount/in_class_field_initializer.rs index 9dd6ef2a..bd55e755 100644 --- a/tests/unit/out/refcount/in_class_field_initializer.rs +++ b/tests/unit/out/refcount/in_class_field_initializer.rs @@ -48,6 +48,7 @@ pub struct S { pub a: Value, pub b: Value, pub c: Value, + pub d: Value, } impl Clone for S { fn clone(&self) -> Self { @@ -55,6 +56,7 @@ impl Clone for S { a: Rc::new(RefCell::new((*self.a.borrow()))), b: Rc::new(RefCell::new((*self.b.borrow()))), c: Rc::new(RefCell::new((*self.c.borrow()).clone())), + d: Rc::new(RefCell::new((*self.d.borrow()).clone())), }; this } @@ -68,23 +70,26 @@ impl Default for S { x: Rc::new(RefCell::new(3)), y: Rc::new(RefCell::new(4)), })), + d: >::default(), } } } impl ByteRepr for S { fn byte_size() -> usize { - 16 + 24 } fn to_bytes(&self, buf: &mut [u8]) { (*self.a.borrow()).to_bytes(&mut buf[0..4]); (*self.b.borrow()).to_bytes(&mut buf[4..5]); (*self.c.borrow()).to_bytes(&mut buf[8..16]); + (*self.d.borrow()).to_bytes(&mut buf[16..24]); } fn from_bytes(buf: &[u8]) -> Self { Self { a: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), b: Rc::new(RefCell::new(::from_bytes(&buf[4..5]))), c: Rc::new(RefCell::new(::from_bytes(&buf[8..16]))), + d: Rc::new(RefCell::new(::from_bytes(&buf[16..24]))), } } } @@ -92,17 +97,12 @@ pub fn main() { std::process::exit(main_0()); } fn main_0() -> i32 { - let s: Value = Rc::new(RefCell::new(S { - a: Rc::new(RefCell::new(1)), - b: Rc::new(RefCell::new(2_u8)), - c: Rc::new(RefCell::new(Inner { - x: Rc::new(RefCell::new(3)), - y: Rc::new(RefCell::new(4)), - })), - })); + let s: Value = Rc::new(RefCell::new(::default())); assert!(((*(*s.borrow()).a.borrow()) == 1)); assert!((((*(*s.borrow()).b.borrow()) as i32) == 2)); assert!(((*(*(*s.borrow()).c.borrow()).x.borrow()) == 3)); assert!(((*(*(*s.borrow()).c.borrow()).y.borrow()) == 4)); + assert!(((*(*(*s.borrow()).d.borrow()).x.borrow()) == 3)); + assert!(((*(*(*s.borrow()).d.borrow()).y.borrow()) == 4)); return 0; } diff --git a/tests/unit/out/unsafe/in_class_field_initializer.rs b/tests/unit/out/unsafe/in_class_field_initializer.rs index 81414d2c..2c393c5f 100644 --- a/tests/unit/out/unsafe/in_class_field_initializer.rs +++ b/tests/unit/out/unsafe/in_class_field_initializer.rs @@ -23,6 +23,7 @@ pub struct S { pub a: i32, pub b: libc::c_char, pub c: Inner, + pub d: Inner, } impl Default for S { fn default() -> Self { @@ -30,6 +31,7 @@ impl Default for S { a: 1, b: (2 as libc::c_char), c: Inner { x: 3, y: 4 }, + d: ::default(), } } } @@ -39,14 +41,12 @@ pub fn main() { } } unsafe fn main_0() -> i32 { - let mut s: S = S { - a: 1, - b: (2 as libc::c_char), - c: Inner { x: 3, y: 4 }, - }; + let mut s: S = ::default(); assert!(((s.a) == (1))); assert!(((s.b as i32) == (2))); assert!(((s.c.x) == (3))); assert!(((s.c.y) == (4))); + assert!(((s.d.x) == (3))); + assert!(((s.d.y) == (4))); return 0; } From 8452469e0abca59585876401f34c07b0960a7a2c Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 10 Sep 2026 14:58:29 +0100 Subject: [PATCH 4/6] Use default for template member with null in-class initializer --- cpp2rust/converter/converter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp2rust/converter/converter.cpp b/cpp2rust/converter/converter.cpp index ea9a0d96..19703652 100644 --- a/cpp2rust/converter/converter.cpp +++ b/cpp2rust/converter/converter.cpp @@ -4024,8 +4024,8 @@ void Converter::EmitDefaultStructLiteral(const clang::RecordDecl *decl) { PushBrace brace(*this); for (auto *field : decl->fields()) { StrCat(GetNamedDeclAsString(field), token::kColon); - if (field->hasInClassInitializer()) { - ConvertVarInit(field->getType(), field->getInClassInitializer()); + if (auto *init = field->getInClassInitializer()) { + ConvertVarInit(field->getType(), init); } else { StrCat(GetDefaultAsString(field->getType())); } From 9a00e2d9070c7d51caa84f42b54839dc078983b4 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 10 Sep 2026 15:53:26 +0100 Subject: [PATCH 5/6] Add template specialization with in-class initializer test --- tests/unit/in_class_field_initializer.cpp | 9 ++++ .../refcount/in_class_field_initializer.rs | 51 +++++++++++++++++++ .../out/unsafe/in_class_field_initializer.rs | 23 +++++++++ 3 files changed, 83 insertions(+) diff --git a/tests/unit/in_class_field_initializer.cpp b/tests/unit/in_class_field_initializer.cpp index 71e83f3e..a99742d9 100644 --- a/tests/unit/in_class_field_initializer.cpp +++ b/tests/unit/in_class_field_initializer.cpp @@ -13,6 +13,12 @@ struct S { Inner d; }; +template struct Boxed { + T v = T(); + int tag = 7; + Boxed(T x, int t) : v(x), tag(t) {} +}; + int main() { S s; assert(s.a == 1); @@ -21,5 +27,8 @@ int main() { assert(s.c.y == 4); assert(s.d.x == 3); assert(s.d.y == 4); + Boxed boxed(5, 9); + assert(boxed.v == 5); + assert(boxed.tag == 9); return 0; }; diff --git a/tests/unit/out/refcount/in_class_field_initializer.rs b/tests/unit/out/refcount/in_class_field_initializer.rs index bd55e755..b6323b96 100644 --- a/tests/unit/out/refcount/in_class_field_initializer.rs +++ b/tests/unit/out/refcount/in_class_field_initializer.rs @@ -93,6 +93,54 @@ impl ByteRepr for S { } } } +#[derive()] +pub struct Boxed_int_ { + pub v: Value, + pub tag: Value, +} +impl Boxed_int_ { + pub fn Boxed_int_(x: i32, t: i32) -> Self { + let x: Value = Rc::new(RefCell::new(x)); + let t: Value = Rc::new(RefCell::new(t)); + let mut this = Self { + v: Rc::new(RefCell::new((*x.borrow()))), + tag: Rc::new(RefCell::new((*t.borrow()))), + }; + this + } +} +impl Clone for Boxed_int_ { + fn clone(&self) -> Self { + let mut this = Self { + v: Rc::new(RefCell::new((*self.v.borrow()))), + tag: Rc::new(RefCell::new((*self.tag.borrow()))), + }; + this + } +} +impl Default for Boxed_int_ { + fn default() -> Self { + Boxed_int_ { + v: >::default(), + tag: >::default(), + } + } +} +impl ByteRepr for Boxed_int_ { + fn byte_size() -> usize { + 8 + } + fn to_bytes(&self, buf: &mut [u8]) { + (*self.v.borrow()).to_bytes(&mut buf[0..4]); + (*self.tag.borrow()).to_bytes(&mut buf[4..8]); + } + fn from_bytes(buf: &[u8]) -> Self { + Self { + v: Rc::new(RefCell::new(::from_bytes(&buf[0..4]))), + tag: Rc::new(RefCell::new(::from_bytes(&buf[4..8]))), + } + } +} pub fn main() { std::process::exit(main_0()); } @@ -104,5 +152,8 @@ fn main_0() -> i32 { assert!(((*(*(*s.borrow()).c.borrow()).y.borrow()) == 4)); assert!(((*(*(*s.borrow()).d.borrow()).x.borrow()) == 3)); assert!(((*(*(*s.borrow()).d.borrow()).y.borrow()) == 4)); + let boxed: Value = Rc::new(RefCell::new(Boxed_int_::Boxed_int_({ 5 }, { 9 }))); + assert!(((*(*boxed.borrow()).v.borrow()) == 5)); + assert!(((*(*boxed.borrow()).tag.borrow()) == 9)); return 0; } diff --git a/tests/unit/out/unsafe/in_class_field_initializer.rs b/tests/unit/out/unsafe/in_class_field_initializer.rs index 2c393c5f..5707c8b0 100644 --- a/tests/unit/out/unsafe/in_class_field_initializer.rs +++ b/tests/unit/out/unsafe/in_class_field_initializer.rs @@ -35,6 +35,26 @@ impl Default for S { } } } +#[repr(C)] +#[derive(Copy, Clone)] +pub struct Boxed_int_ { + pub v: i32, + pub tag: i32, +} +impl Boxed_int_ { + pub unsafe fn Boxed_int_(mut x: i32, mut t: i32) -> Self { + let mut this = Self { v: x, tag: t }; + this + } +} +impl Default for Boxed_int_ { + fn default() -> Self { + Boxed_int_ { + v: 0_i32, + tag: 0_i32, + } + } +} pub fn main() { unsafe { std::process::exit(main_0() as i32); @@ -48,5 +68,8 @@ unsafe fn main_0() -> i32 { assert!(((s.c.y) == (4))); assert!(((s.d.x) == (3))); assert!(((s.d.y) == (4))); + let mut boxed: Boxed_int_ = Boxed_int_::Boxed_int_({ 5 }, { 9 }); + assert!(((boxed.v) == (5))); + assert!(((boxed.tag) == (9))); return 0; } From 77a11f11a22e16969c5e2b326c62d99a2915dcc5 Mon Sep 17 00:00:00 2001 From: Lucian Popescu Date: Thu, 10 Sep 2026 15:57:52 +0100 Subject: [PATCH 6/6] Add explaining comment --- tests/unit/in_class_field_initializer.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/unit/in_class_field_initializer.cpp b/tests/unit/in_class_field_initializer.cpp index a99742d9..70015ccf 100644 --- a/tests/unit/in_class_field_initializer.cpp +++ b/tests/unit/in_class_field_initializer.cpp @@ -13,6 +13,13 @@ struct S { Inner d; }; +// Boxed::tag is in-class initialized. However the default constructor is never +// instantiated, only the explicit one is used. +// +// Because no default constructor is instantiated, the specialization does not +// contain the in-class initializer. In Rust, the Default trait initializes +// Boxed::tag with 0. This is correct because the C++ program never reads the +// in-class initializer of Boxed::tag, hence Rust also does not read it. template struct Boxed { T v = T(); int tag = 7;