Skip to content

gui_ADHABsmK9FXz shouldn't recommend using transmute for float <-> int and int <-> ptr #420

@inkreasing

Description

@inkreasing

A pointer-to-address cast is not symmetrical because the resulting pointer may not point to a valid object, may not point to an object of the right type, or may not be properly aligned. If a conversion in this direction is needed, std::mem::transmute will communicate the intent to perform an unsafe operation.

While the idea of using transmute to communicate the unsafety of the operation sounds good it's bad advice. as and transmute aren't equivalent when using them on integers and pointers. transmute doesn't use exposed provenance, while as does. This should recommend the exposed_provenance APIs. These aren't unsafe, but at least the resulting pointers are usable.

This is the compliant example. It does multiple questionable things. I added my comments with a + prefixed. Everything i am noting here is also noted by the explictly allowed lints here.

use std::convert::TryInto;

#[allow(dead_code)]
fn f2(x: u16, y: i32, _z: u64, w: u8) {
  let _a: char            = w.into();
  let _b: Result <u32, _> = y.try_into(); // produce an error on range clip
  let _c: i64             = x.into();

  let d = f32::from(x);  // u16 is within range, u32 is not
  let _e = f64::from(d);
+ (this should probably be _e)
  // let f = f32::from(e); // no From exists

  // let g = ...            // no From exists

  let h: u32 = 0;
  let p1: * const u32 = &h;
+ this exposes the provenance so the integer can be turned back into a pointer
  let a1 = p1 as usize;     // (compliant)

  unsafe {
+ these don't expose provenance, so these integers can't be turned back into valid pointers.
+ if that was the wanted behaviour this should use the addr method.
    let _a2: usize = std::mem::transmute(p1);  // OK
    let _a3: u64   = std::mem::transmute(p1);  // OK, size is checked
    // let a3: u16   = std::mem::transmute(p1);  // invalid, different sizes

+ both of these pointers are invalid, because they have no provenance.
+ if as would have been used, they would have been valid
    #[allow(integer_to_ptr_transmutes)]
    let _p2: * const u32 = std::mem::transmute(a1); // OK
    #[allow(integer_to_ptr_transmutes)]
    let _p3: * const u32 = std::mem::transmute(a1); // OK
  }

+ the from_bits to_bits functions do exactly that without requiring unsafe.
  unsafe {
    // does something entirely different,
    // reinterpreting the bits of z as the IEEE bit pattern of a double
    // precision object, rather than converting the integer value
    #[allow(unnecessary_transmutes)]
    let _f1: f64 = std::mem::transmute(_z);
  }
}

fn main() {}

(by valid pointer i mean a pointer with provenance. having a pointer without provance of course isn't UB, but pretty useless)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions