Boxing
In the refcount model a variable is boxed: its type T is wrapped in
Value<T>, an alias for Rc<RefCell<T>> (see
Reference Counting). Without the box, taking the address
of a variable would need a Rust reference, and arbitrary C++ aliasing cannot be
expressed with references.
Not every type position is boxed. ConverterRefCount keeps a stack of
conversion kinds, conversion_kind_, and the construct
that owns the type pushes one before printing it:
FullRefCount: pushed by variable declarations;Convert(QualType)wraps the result inValue<...>.Pointee: pushed by field declarations; the bare type is printed. Fields that are arrays, or whose type maps to aVecor aBox(std::vector,std::string,std::array), pushFullRefCountinstead (see below).Unboxed: pushed by parameter lists, return types, and record names; the bare type is printed.Ptr: pushed by a pointer type for its pointee; also printed bare.
The result by position:
| Position | int | Item | int[3] |
|---|---|---|---|
| local variable, global | Value<i32> | Value<Item> | Value<Box<[i32]>> |
| function parameter, return type | i32 | Item | decays to Ptr<i32> |
| struct field | i32 | Item | Value<Box<[i32]>> |
pointee of Ptr<T>, element of a container | i32 | Item | Box<[i32]> |
Parameters arrive unboxed and are re-boxed by the function preamble; return values are unboxed:
int add(int a, Item item) { return a + item.id; }
#![allow(unused)]
fn main() {
pub fn add_0(a: i32, item: Item) -> i32 {
let a: Value<i32> = Rc::new(RefCell::new(a));
let item: Value<Item> = Rc::new(RefCell::new(item));
return *a.borrow() + (*item.borrow()).id;
}
}
C++ passes arguments to functions by copy, so signatures stay unboxed; boxing
the copy on entry then lets the body treat parameters exactly like local
variables. The preamble skips reference parameters, which are a Ptr<T> and
never boxed.
Nested containers, library ones and arrays alike, box each level except the
innermost, so that every inner container can be borrowed and mutated on its own,
and a pointer can be taken to it. The boxing is written into the type rules
themselves: std::vector<std::vector<int>> maps to Vec<Value<Vec<i32>>>, and
the carray rules map int a[2][2] to Box<[Value<Box<[i32]>>]>, both before
the outer Value<...> of the declaration is added.
Struct fields are stored inline, so that a whole struct is a single allocation,
and a pointer to a field records the struct’s allocation and the field’s byte
offset (see Pointers). Arrays and vectors are the exception: an
array field is a Value<Box<[T]>> of its own, and a vector field a
Value<Vec<T>>. A pointer to an element, or to a field of an element, then has
the array or the vector as its allocation instead of the struct, and pointer
arithmetic moves between elements as for any other array:
struct Holder { std::vector<Point> points; int n; };
h.points[0].y = 5;
#![allow(unused)]
fn main() {
pub struct Holder {
#[offset(0)]
pub points: Value<Vec<Point>>,
#[offset(24)]
pub n: i32,
}
(*(*h.borrow()).points.borrow_mut())[(0_usize) as usize].y = 5;
}
An array or vector field is accessed like a local one, through its own
borrow() or borrow_mut(), and the struct is only borrowed immutably to reach
it. As Value is shared on clone(), structs with such fields implement
Clone by copying the arrays and vectors, instead of deriving it.