I am trying to create a graph structure (for a type system). Specifically, I have a structure that uses arena-based indices to represent the graph and I want to transform that into one that uses direct references (so that I don't need an additional arena argument everywhere to resolve those indices to the actual objects).
I am using unsafe
in one place, and it all seems to work fine, but I am not sure if it is safe. Miri does give an undefined behavior error. I see that the Nomicon says "Transmuting an & to &mut is UB." (in the context of mem::transmute
, but probably applies to casts as I have done here).
I believe that the way I am using external immutability, it wouldn't be possible to encounter UB. Miri errors make me nervous, but I am not sure what is the failure scenario in my code? Is my unsafe
code really unsafe? Is there a usage example, where this would lead to an actual memory or other error?
The full code along with tests is in playground.
Here is the code that does uses unsafe
:
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| {
(
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| {
let field_type = resolved_types.get(field.typ).unwrap();
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.
#[allow(clippy::cast_ref_to_mut)]
let resolved_type: &mut Type = unsafe { &mut *(resolved_type as *const Type as *mut Type) };
resolved_type.fields = resolved_fields;
}
System {
types: resolved_types,
}
}
The following structures use my own IdArena
to make it work in playground (the real code uses one from id_arena for type safety):
pub struct ArenaSystem {
pub types: Arena<ArenaType>,
}
pub struct ArenaType {
pub name: String,
pub fields: Vec<ArenaField>,
}
pub struct ArenaField {
pub name: String,
pub typ: Id<ArenaType>,
}
And here are structures that use direct references:
pub struct System<'system> {
types: Vec<Type<'system>>,
}
#[derive(Debug)]
pub struct Type<'system> {
pub name: String,
pub fields: Vec<Field<'system>>,
}
pub struct Field<'system> {
pub name: String,
pub typ: &'system Type<'system>,
}
impl<'system> Debug for Field<'system> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}: {}", self.name, self.typ.name)
}
}