Reputation: 1054
Let me preface with saying that I'm actively looking into other options than transmute
, I was just suprised that transmute
doesn't compile in this case.
I'm working on a Builder
derive library, that checks at compile time which fields are initialized. For this purpose I'm using const generics. I got the basics working, but I'm trying to add a feature, and because of that I was looking at transmute
.
If I'd use transmute
, an example of a setter would look as follows:
#[derive(Builder)]
pub struct Foo<const SIZE: usize> {
bar: [usize; SIZE],
}
// Just the setter part of the macro expansion:
impl<const SIZE: usize> FooBuilder<SIZE, false> {
pub fn bar(mut self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
self.data.bar = Some(bar);
unsafe { std::mem::transmute(self) }
}
}
But transmute doesn't work like this. SIZE
is exactly the same and the boolean doesn't have any effect on the underlying data, but still I get:
cannot transmute between types of different sizes, or dependently-sized types
source type: `test::const_generic::FooBuilder<SIZE, false>` (size can vary because of [usize; SIZE])
target type: `test::const_generic::FooBuilder<SIZE, true>` (size can vary because of [usize; SIZE])
I was just curious why this restriction is in place. Can anyone explain this to me?
// simplified macro expansion:
pub struct Foo<const SIZE: usize> {
bar: [usize; SIZE],
}
impl <const SIZE: usize> Foo<SIZE> {
fn builder() -> FooBuilder<SIZE, false> {
FooBuilder::new()
}
}
pub struct FooBuilder<const SIZE: usize, const __BUILDER_CONST: bool> {
bar: Option<[usize; SIZE]>,
}
impl<const SIZE: usize> FooBuilder<SIZE, false> {
pub fn new() -> FooBuilder<SIZE, false> {
FooBuilder {
bar: None,
}
}
}
impl<const SIZE: usize> FooBuilder<SIZE, false> {
pub fn bar(mut self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
self.bar = Some(bar);
unsafe { std::mem::transmute(self) }
// ^ Should work, because the value of `__BUILDER_CONST` has no influence
// on the layout of the struct, and `SIZE` is identical
}
}
impl<const SIZE: usize> FooBuilder<SIZE, true> {
pub fn build(self) -> Foo<SIZE> {
Foo::<SIZE> {
bar: self.bar.unwrap()
}
}
}
Btw, the actual setter looks more as follows, I was just running into some weird lifetime issues when adding a new feature (using impl AsRef<T>
as a setter input type)
impl<const SIZE: usize> FooBuilder<SIZE, false> {
pub fn bar(self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
let mut data = self.data;
data.bar = Some(bar);
FooBuilder { data }
}
}
Upvotes: 0
Views: 319
Reputation: 71330
The analysis in the compiler is just too restrictive. It doesn't allow any transmute that depends on dynamic size even when we can prove the sizes are equal.
However, note that in your case the transmute is incorrect: you need to add #[repr(transparent)]
to the struct.
Upvotes: 1