user1244932
user1244932

Reputation: 8092

Is there a universal Rust pointer type that can store any other kind of pointer, an analog of C's void *?

I want to create a C FFI API for my crate, but it's not clear how safe it is to cast pointers. Pseudocode:

#[no_mangle]
extern "C" fn f(...) -> *mut c_void {
    let t: Box<T> = ...;
    let p = Box::into_raw(t);
    p as *mut c_void
}

This works as expected, but how safe is it? In C or C++, there is special void * pointer and the C++ standard declares that it is safe to cast to it. Potentially, sizeof(void *) may be not equal sizeof(T *), but there is a guarantee that sizeof(void *) >= sizeof(T *).

What about Rust? Is there any guarantee about the std::mem::size_of of a pointer or safe casting between pointers? Or do all pointers have equal size by implementation, equal to usize?

By "universal", I mean that you can convert X * without losing anything. I do not care about type information; I care about different sizes of pointers to different things, like near/far pointers in the 16-bit days.

4.10 says

The result of converting a "pointer to cv T" to a "pointer to cv void" points to the start of the storage location where the object of type T resides,

It is impossible that sizeof(void *) < sizeof(T *), because then it is impossible to have real address of storage location.

Upvotes: 8

Views: 4298

Answers (1)

Shepmaster
Shepmaster

Reputation: 430663

No.

Rust's raw pointers (and references) currently come in two flavors:

  • thin (one native-sized integer in size)
  • fat (two native-sized integers in size)
use std::mem;

fn main() {
    println!("{}", mem::size_of::<*const u8>());   //  8
    println!("{}", mem::size_of::<*const [u8]>()); // 16
}

There's no type that allows storing both; even the Big Hammer of mem::transmute won't work:

use std::mem;

unsafe fn example(mut thin: *const u8, mut fat: *const [u8]) {
    fat = mem::transmute(thin);
    thin = mem::transmute(fat);
}
error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> src/main.rs:4:11
  |
4 |     fat = mem::transmute(thin);
  |           ^^^^^^^^^^^^^^
  |
  = note: source type: `*const u8` (64 bits)
  = note: target type: `*const [u8]` (128 bits)

error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
 --> src/main.rs:5:12
  |
5 |     thin = mem::transmute(fat);
  |            ^^^^^^^^^^^^^^
  |
  = note: source type: `*const [u8]` (128 bits)
  = note: target type: `*const u8` (64 bits)

Since the layout of fat pointers is a Rust-specific concept, they should never be accessed via FFI. This means that only thin pointers should be used, all of which have a uniform known size.

For those types, you should use an opaque pointer to provide better type safety. You could also use *const () or *const libc::c_void.

See also:

In C or C++, there is special void * pointer and the C++ standard declares that it is safe to cast to it.

This isn't always true:

Upvotes: 10

Related Questions