Alternative way to convert a struct to another with a differing set of derive?

Hi,

I use rust for my code base and bevy for html projection.
All is well and works perfectly.

Call "my code base" the code I wrote out of Bevy.

I decided to keep my structs separated because i don't want to have a bevy dep in my code base that is not directly concerned, to keep interests separated.

Maybe one day we won't use Bevy anymore and I don't want to chase every bit of it everywhere.

So here is the deal:
Say I have

pub struct StructTypeB{
...
}
#[derive(Debug,Clone)]
pub struct MyA{
       pub vec : Vec<StructTypeB>,
    .. many fields
}

in my code base

and a similar

pub struct BevyStructTypeB{
...
}

#[derive(Debug,Clone, Resource)]
pub struct BevyA{
    .. many fields, exact same structure
}

which has the exact same structure.

I convert MyA to BevyA at the beginning of the program, cloning everything everywhere.
I could create an impl to convert from MyA to BevyA but the issue remains the same.
The issue is that BevyA adds the Resource attribute.

It would be nice if in Bevy, I could directly use A from my code base.

I am not sure if a transmute is a solution ( I need your opinion on this one).

So far the solutions:

  1. Keep doing like I do, possibly writing an impl from MyA to BevyA
  2. #[cfg(feature = "ToBevy")] in my code base to activate or not specific game engines. Okay-ish as it clutters the code base.
  3. Transmute (not sure on this one)
  4. Your solution

How would you deal with it ?

Thanks

Transmute is never the solution.
Especially not when you can't decide on your own. Transmuting is highly unsafe, highly advanced territory. You apparently didn't consider even the most basic requirements for transmuting (eg. the fact that Rust structs have no guaranteed layout), so you'll get it wrong and cause UB with very high probability.

If you want independence between your core logic and a particular framework, then you'll have to write bi-directional conversions for everything that touches the API boundary. (And have the framework-specific code depend on the framework-agnostic part.)

If the types are similar enough structurally so that the conversion can be performed mechanistically (probably the case here), then you can write a derive macro for removing the conversion boilerplate.

2 Likes

Transmuting between two types that have identical field lists is not, in fact, well-defined. The compiler is free to reorder and repack fields in structs, and the language does not require that identical struct definitions result in identical layouts.

The way I'd approach this:

  1. Choose not to care. That is, the effort of chasing Bevy out of the code, while nontrivial, is probably not so high as to justify pre-emptively designing and maintaining this kind of a system, especially when the relevant traits can be #[derive(…)]d.

  2. If that's not acceptable for one reason or another, then use a newtype rather than duplicating the whole type taxonomy:

    pub struct StructTypeB{
        // ...
    }

    #[derive(Debug, Clone)]
    pub struct MyA{
        pub vec : Vec<StructTypeB>,
        // .. many fields
    }

    #[derive(Debug, Clone, Resource)]
    pub struct BevyA(MyA);
  1. If that's not acceptable, then look into writing a proc macro that can generate the BevyFoo type for a given Foo type, to minimize the amount of manually-maintained duplicate structs kicking around my program.

  2. If none of those are satisfactory, maintain duplicate structs by hand, and write From impls to make the ergonomics of converting between them manageable. It's a ton of work, and relatively error prone, but it works reliably and requires little code support.

4 Likes

You are right, I thought about that alternative while writing.

Thanks for the rest of your inputs, really appreciated!

I really like 2, this is what I am going after.
Thanks derspiny!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.