Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

std::mem::transmute doesn't compile even though both types are guaranteed to have the same size. #116585

Open
koenichiwa opened this issue Oct 9, 2023 · 7 comments
Labels
A-const-generics Area: const generics (parameters and arguments) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@koenichiwa
Copy link

Let me preface that I'm actively looking into other options than transmute, but I expected this to work, because it could basically be an no-op. Also the doc on transmute says: "Compilation will fail if this is not guaranteed", and these types are essentially the same.

I'm working on a Builder derive and I'm working on the implementation of the setters. I was trying something new.

I tried this code:

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) }
    }
}

It's part of a bigger piece of code:

// simplified macro expansion:
pub struct Foo<const SIZE: usize> {
    bar: [usize; SIZE],
}

pub struct FooData<const SIZE: usize> {
    pub bar: Option<[usize; SIZE]>,
}

impl<const SIZE: usize> Builder for Foo<SIZE> {
    type Builder = FooBuilder<SIZE, false>;
    fn builder() -> FooBuilder<SIZE, false> {
        self::Builder::new()
    }
}

pub struct FooBuilder<const SIZE: usize, const __BUILDER_CONST_0: bool> {
    data: FooData<SIZE>,
}

impl<const SIZE: usize> FooBuilder<SIZE, false> {
    pub fn new() -> FooBuilder<SIZE, false> {
        data: FooData<SIZE> {
            bar: None,
        }
    }
}

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) } 
              // ^^^^^^^^^^^^^^^^^^^ Here I call the transmute
    }
}

impl<const SIZE: usize> FooBuilder<SIZE, true> {
    pub fn build(self) -> Foo<SIZE> {
        Foo<SIZE> {
            bar: self.data.bar.unwrap()
        }
    }
}

I expected to see this happen: Because both the input and the output are guaranteed to be the same size, I expected it to compile.

Instead, this happened: It didn't compile, and I got this error:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> const_typed_builder_test/src/lib.rs:693:32
    |
693 |     #[derive(Debug, PartialEq, Builder)]
    |                                ^^^^^^^
    |
    = note: source type: `test::const_generic::FooBuilder<SIZE, false>` (size can vary because of [usize; SIZE])
    = note: target type: `test::const_generic::FooBuilder<SIZE, true>` (size can vary because of [usize; SIZE])
    = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)

Meta

rustc --version --verbose:

rustc 1.74.0-beta.1 (b5c050feb 2023-10-03)
binary: rustc
commit-hash: b5c050febf10c9bcc0459d41fe2a1e190ad30b8d
commit-date: 2023-10-03
host: x86_64-apple-darwin
release: 1.74.0-beta.1
LLVM version: 17.0.2

I also ran it on the latest nigthly and stable, with the same response

rustc 1.75.0-nightly (2e5a9dd6c 2023-10-02)
binary: rustc
commit-hash: 2e5a9dd6c9eaa42f0684b4b760bd68fc27cbe51b
commit-date: 2023-10-02
host: x86_64-apple-darwin
release: 1.75.0-nightly
LLVM version: 17.0.2
rustc 1.72.1 (d5c2e9c34 2023-09-13)
binary: rustc
commit-hash: d5c2e9c342b358556da91d61ed4133f6f50fc0c3
commit-date: 2023-09-13
host: x86_64-apple-darwin
release: 1.72.1
LLVM version: 16.0.5
Backtrace

I'm not sure what I'm doing wrong but if I run `cargo test --workspace -- -Z macro-backtrace` I only get this:

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
   --> const_typed_builder_test/src/lib.rs:693:32
    |
693 |     #[derive(Debug, PartialEq, Builder)]
    |                                ^^^^^^^
    |
    = note: source type: `test::const_generic::FooBuilder<SIZE, false>` (size can vary because of [usize; SIZE])
    = note: target type: `test::const_generic::FooBuilder<SIZE, true>` (size can vary because of [usize; SIZE])
    = note: this error originates in the derive macro `Builder` (in Nightly builds, run with -Z macro-backtrace for more info)

@koenichiwa koenichiwa added the C-bug Category: This is a bug. label Oct 9, 2023
@rustbot rustbot added the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 9, 2023
@koenichiwa koenichiwa changed the title std::mem::transmute doesn't compile when both types are guaranteed to have the same size. std::mem::transmute doesn't compile even though both types are guaranteed to have the same size. Oct 9, 2023
@workingjubilee workingjubilee added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. A-const-generics Area: const generics (parameters and arguments) T-types Relevant to the types team, which will review and decide on the PR/issue. labels Oct 9, 2023
@saethlin saethlin removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Oct 10, 2023
@ouz-a
Copy link
Contributor

ouz-a commented Oct 10, 2023

Could you provide an example without dependencies.

@koenichiwa
Copy link
Author

Could you provide an example without dependencies.

Yes, made an MRE

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()
        }
    }
}

@ouz-a
Copy link
Contributor

ouz-a commented Oct 10, 2023

I opened up the compiler to see what's going on and after playing around with it, it makes sense for this to not compile because bool false != bool true. But when they are identical compiler let's this compile, maybe we could ask @RalfJung for details of why this is not complaining.

Code below compiles fine.

const hey:bool = false;

pub struct Foo<const SIZE: usize> {
    bar: [usize; SIZE],
}

impl <const SIZE: usize> Foo<SIZE> {
    fn builder() -> FooBuilder<SIZE, hey> {
        FooBuilder::new()
    }
}

pub struct FooBuilder<const SIZE: usize, const __BUILDER_CONST: bool> {
    bar: Option<[usize; SIZE]>,
}

impl<const SIZE: usize> FooBuilder<SIZE, hey> {
    pub fn new() -> FooBuilder<SIZE, hey> {
        FooBuilder {
            bar: None,
        }
    }
}

impl<const SIZE: usize> FooBuilder<SIZE, hey> {
    pub fn bar(mut self, bar: [usize; SIZE]) -> FooBuilder<SIZE, hey> {
        self.bar = Some(bar);
        unsafe { std::mem::transmute(self) } 
    }
}

impl<const SIZE: usize> FooBuilder<SIZE, hey> {
    pub fn build(self) -> Foo<SIZE> {
        Foo::<SIZE> {
            bar: self.bar.unwrap()
        }
    }
}

@asquared31415
Copy link
Contributor

Minimized:

#![allow(dead_code)]

// an explicit repr does not resolve the issue
pub struct UwU<const N: usize, const M: usize> {
    _arr: [u8; N],
}

// note: -> UwU<N, N> or any other value that's not exactly M fails to compile too
fn meow<const N: usize, const M: usize>(x: UwU<N, M>) -> UwU<N, 10> {
    unsafe { core::mem::transmute(x) }
}

Having a type with a size dependent on a const generic and then adding any const generic that does not match between the type, whether or not it affects the final size of the computation, causes the transmute to not compile.

@jdahlstrom
Copy link

jdahlstrom commented Oct 11, 2023

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

Note that in the general case, transmuting between Foo<N> and Foo<M> is not sound by default, even if none of Foo's fields depends on the generic parameter. You have to annotate FooBuilder as #[repr(C)] or #[repr(transparent)] to guarantee their memory representations are identical. But as @asquared31415 said, this issue occurs regardless of repr used.

@RalfJung
Copy link
Member

Yeah transmute size checks aren't infinitely smart. I'm not familiar with how they work though, so I can't help here.

There's always the work-around of using transmute_copy (but remember to protect against double-drop).

@JakkuSakura
Copy link

I can provide two interesting cases, and the error reporting is misleading. Seems like the compiler can infer that T is of the same size, but fails to reason that A is always the same A

use std::marker::PhantomData;
use std::mem::transmute;

struct S<T> {
    a: PhantomData<T>,
}
struct Foo<T, A> {
    s: T,
    a: A,
}
fn foo<A>(a: A) -> Foo<S<u64>, A> {
    unsafe {
        let foo = Foo {
            s: S {
                a: PhantomData::<u32>,
            },
            a,
        };
        transmute(foo)
    }
}
fn bar() -> Foo<S<u64>, ()> {
    unsafe {
        let bar = Foo {
            s: S {
                a: PhantomData::<u32>,
            },
            a: ()
        };
        transmute(bar)
    }
}
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
  --> src/main.rs:19:9
   |
19 |         transmute(foo)
   |         ^^^^^^^^^
   |
   = note: source type: `Foo<S<u32>, A>` (size can vary because of A)
   = note: target type: `Foo<S<u64>, A>` (size can vary because of A)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-const-generics Area: const generics (parameters and arguments) C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

9 participants