7

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.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user1244932
  • 7,352
  • 5
  • 46
  • 103
  • 1
    You don't cast between pointers in Rust (and you don't do that in C++ as well!). Instead use traits. I don't see how this question is related to FFI. Please clarify your question. – hellow Jun 04 '19 at 11:13
  • "Rust C FFI universal pointer type, analog of “void *”?" no no no and no. Sorry ;). `void *` is not a "magic" pointer, it's not "universal". It's a nothing pointer type. One must know it's true type to be able to use it. There is nothing universal, a pointer of `struct A` can't be convert to `void *` and then convert to a pointer to `struct B`. So it's clearly not universal. Of course my note is just pedantic, but it's important to know. – Stargateur Jun 04 '19 at 12:02
  • @hellow `t as *mut c_void` is cast of pointers from my point of view, what term do you want here? – user1244932 Jun 04 '19 at 12:03
  • "but there is guarantee that `sizeof(void *) >= sizeof(T *)`", can you quote the standard that say that ? In C, I really doubt of that, I don't know for C++. – Stargateur Jun 04 '19 at 12:03
  • 1
    @Stargateur universal in terms that you convert `X *` without losing anything, – user1244932 Jun 04 '19 at 12:05
  • @Stargateur 4.10 Pointer conversions 2 An rvalue of type "pointer to cv T," where T is an object type, can be converted to an rvalue of type "pointer to cv void." – user1244932 Jun 04 '19 at 12:06
  • Your question is unclear, tho. In Rust, there is something somewhat analog to the void pointer, that is the `Any` trait, but this has nothing to do with FFI – Boiethios Jun 04 '19 at 12:09
  • @user1244932 You didn't understand what I try to explain probably my fault, explain `void *` in comment is impossible combine with my poor english. When you convert `X *` to `void *`, you loose type information. You do loose something, and sometime very important. Your quote doesn't at all justify `sizeof(void *) >= sizeof(T *)` – Stargateur Jun 04 '19 at 12:11
  • @Stargateur I do not care about type information at all. I am care about different pointers size to different things. Like far/near pointers in old 16bit days. `4.10` clear 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`, so it is impossible that `sizeof(void *) < sizeof(T *)`, becase then it is impossible to have real address of `storage location`. – user1244932 Jun 04 '19 at 12:17
  • The only thing I found in C, is that `sizeof(void *) == sizeof(character_type *)` same for alignment requirement. "so it is impossible that sizeof(void *) < sizeof(T *), becase then it is impossible to have real address of storage location." Yes it is possible. Nothing in all C standard disallow pointer to be whatever then want, remember C is very flexible to implement, some embedded system have a strange implementation. I mean in theory a pointer could be just a hash, and each time you use a pointer the implementation could just do a lot of thing that end up by doing what you ask for. – Stargateur Jun 04 '19 at 12:21
  • @Stargateur Universal in the sense that it can hold pointers of any kind and can be casted back to the original pointer type, which is what void* is used for in C/C++. – chila Jul 01 '23 at 20:15

1 Answers1

9

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:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • In fact I don't care about fat pointers. I care about case when `size_of` `*mut T1` and `size_of` `*mut T2` is not equal. In C/C++ standard there is no garantee that sizeof pointers are equal, there is only garantee that you can convert pointers to/from `void *` without information lost. So in Rust all normal pointers have the same size? – user1244932 Jun 04 '19 at 15:15
  • @user1244932 There is no Rust standard so it's impossible to state with 100% certainty, but as far as I know, **yes**, all thin pointers are the same size. – Shepmaster Jun 04 '19 at 15:22