This is a follow up from Unsafe code review request - #8 by ramnivas.
The only difference is the use of UnsafeCell
to make changes behind a shared reference following the suggestion by @2e71828). As far as understand, the use of UnsafeCell
indicates to the optimizer to not assume that a shared reference is immutable (which was the issue with my original code).
From the UnsafeCell docs:
If you have a reference
&T
, then normally in Rust the compiler performs optimizations based on the knowledge that&T
points to immutable data. ...UnsafeCell<T>
opts-out of the immutability guarantee for&T
: a shared reference&UnsafeCell<T>
may point to data that is being mutated.
(There is an additional change to introduce types()
following @quinedot 's suggestion, but that is probably orthogonal to the core issue).
However, running cargo +nightly miri test
still gives me the same error. Although, it hedges (with the original and new code): "but the Stacked Borrows rules it violated are still experimental", this error gives me a pause that there is still a UB lurking there.
So is my code safe?
Here it the transform
function (the rest of the code is in playground:
pub fn transform(arena_system: ArenaSystem) -> System<'static> {
// First create shallow types (i.e. type with no fields), since we may not
// have created a type that a field refers to.
let (resolved_types, arena_types_fields): (Vec<_>, Vec<_>) = arena_system
.types
.data
.into_iter()
.map(|arena_type| {
(
UnsafeCell::new(Type {
name: arena_type.name,
fields: Vec::new(),
}),
arena_type.fields,
)
})
.unzip();
// Now fill in the fields
for (resolved_type, arena_type_fields) in
resolved_types.iter().zip(arena_types_fields.into_iter())
{
let resolved_fields: Vec<_> = arena_type_fields
.into_iter()
.map(|field| {
// SAFETY: Getting an immutable reference to the content inside UnsafeCell
let field_type = unsafe { &*resolved_types.get(field.typ).unwrap().get() };
Field {
name: field.name,
typ: field_type,
}
})
.collect();
// SAFETY: All the resolved types are placed in the `resolved_types`
// vector and will not move ever (we don't need to resize
// `resolved_types` ever). Furthermore, we place `resolved_types` in the
// `System` struct, which is immutable (since the `types` field is
// private), so nothing will get added to `types` in it.
let resolved_type: &mut Type = unsafe { &mut *resolved_type.get() };
resolved_type.fields = resolved_fields;
}
System {
types: resolved_types.into_iter().map(|t| t.into_inner()).collect(),
}
}