Generic Sized type does not have fixed size

Hello,
While trying to optimize the function :

fn btree__val<T>(a: btree) -> T {
    match a {
        btree::node1(node1) => unsafe {
            std::mem::transmute_copy(&Rc_unwrap_or_clone(node1.val))
        }
        btree::node2(ref node2) => unsafe {
            std::mem::transmute_copy(&Rc_unwrap_or_clone(node2.val)) //node2.val may not have the same type as node1.val
        }
        _ => unreachable!(),
    }
}

I discovered that transmute_copy does copy the data even if there is no need to do that. So, I tried to use transmute instead :

fn btree__val<T:Sized>(a: btree) -> T {
    match a {
        btree::node1(node1) => unsafe {
            std::mem::transmute(Rc_unwrap_or_clone(node1.val)) // example of change
        }
        btree::node2(ref node2) => unsafe {
            std::mem::transmute_copy(&Rc_unwrap_or_clone(node2.val))
        }
        _ => unreachable!(),
    }
}

but I get the following error:

cannot transmute between types of different sizes, or dependently-sized types
source type: `f32` (32 bits)
target type: `T` (this type does not have a fixed size)

that's strange because the size of T is actually known at compile time ! (and in that case we are always transmuting to the same type)

Do you know what causes the error ?

I know it looks like issue 43408; but I don't understand if a solution was found.

Thanks

It's a generic type parameter. Each generic will have a fixed size (as T: Sized), but each one can have different sizes from one another, and from f32. Nothing is preventing you from calling btree__val::<[u8; 1000000]>(a).

You haven't shared any of the surrounding context, but that transmute is unsound. (Nothing is stopping me from calling btree__val::<&mut String> either, say.)

2 Likes

In facts, I just want to do a C-like cast, a simpler example is:

fn strange_error<T:Sized>(a : f32) -> T{
    unsafe{std::mem::transmute(a)}
}

and I know for sure that T will be the right size.

I know this is unsound, but that's in a broader context of generated code (that I try to optimize), and the transmute is superfluous : it will always be type T to type T, I just put it because I need a generic type.

How exactly is node1.val an f32 here? Shouldn't it be a T where T: Clone, in which case no casting is necessary?

Additionally, be very cautious of storing arbitrary bytes in f32s. Many CPUs will NaN-canonicalize your non-floating-point data.

EDIT : sorry, I forgot a part of the code
The code is:

#[derive(Clone, PartialEq)]
enum btree {
    leaf(leaf),
    node1(node1),
    node2(node2),
}

#[derive(Clone, PartialEq)]
struct leaf {}

#[derive(Clone, PartialEq)]
struct node1 {
    val: Rc<f32>,
    right: Rc<btree>,
}

#[derive(Clone, PartialEq)]
struct node2 {
    val: Rc<i32>, //for illustration that an accessor can return two different types
    right: Rc<btree>,
    left: Rc<btree>,
}

It is not arbitrary data that I put in f32, but only f32 data. When my function btree__val is called with node1, it will always be in a code like:

let a : f32 = btree__val(whatever_node1)

so T will be f32: cast is pointless (but it helps Rust to understand).

(and that the same with node2, you just replace f32 by i32)

The Rust way to do this is to provide two different fallible accessor functions:

mod btree {
  // ...
  fn get_i32(n: btree) -> Option<i32> { match n {
    btree::node1(node) => { return Some(node.val) }
    btree::node2 => { return None; }
    btree::leaf => { return None; }
  } }
  unsafe fn get_i32_unchecked(n: btree) -> i32 {
    unsafe { get_i32(n).unwrap_unchecked() }
  }
  // same for f32...
  fn get_leaf(n: btree) -> Option<()> { match n {
    btree::leaf => { return Some(()); }
    _ => { return None; }
  } }

Additionally, you should generally be using either Cell<f32> (you want to modify the number), Pin<Rc<f32>> (you care about the unique address), or Arc<AtomicF32> (you want to concurrently modify the number) instead of Rc<f32>.

Edit 2: Also, this does not appear to be a btree?

This is a binary tree node, not a B-Tree which would have something more like children: [Option<Rc<btree>>; 6]. Check the standard library's BTreeMap.

1 Like

Use bytemuck::cast.

1 Like

Ok thanks, I think I can create different accessors depending on the return type. Yes btree is not a binary tree or a Rust BTree, it's just an example.

Yes, I indeed tried to use that, but it's quite a big change of all my code (to remove the padding bytes everywhere, and implement NoUninit)

I was going off of the types you'd mentioned so far.

However, if you're transmuting from something with padding bytes to something without, you have UB.

More generally, much of the point of bytemuck is being able to do such things without UB, and -- via their derive macros -- even without any of your own unsafe. So if you have to do more than slap #[derive(NoUninit)] (or Pod or whatever) on your data types, such as removing padding bytes, that's a sign that you're probably doing something UB.

3 Likes

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.