What is the different between `*const T` and `*const u8`?

I saw in many place, They get pointer (*const u8) from a pointer type (*const _) by casting it,

For example,

struct MyType { data: [u16; 8] }
impl MyType {
    fn ptr(&self) -> *const Self { self as *const Self }
    fn as_ptr(&self) -> *const u8 { self.ptr() as *const u8 }
}
fn main() {
    let my_type = MyType { data: [0; 8] };
    let pt = my_type.ptr();
    let rp = my_type.as_ptr();
    let dp = my_type.data.as_ptr();
    println!("pt: {:?}, rp: {:?}, dp: {:?}", pt, rp, dp);
    // pt: 0x7ffc18c27560, rp: 0x7ffc18c27560, dp: 0x7ffc18c27560
}

So my thought is every pointer*const is same, And *const T is just known value ( type T ) for a pointer ? And *const u8 raw pointer of memory ? It's just my thought...

What is the different between *const T and *const u8 ?

1 Like

The type of T matters for things like read and swap and offset, etc.

Additionally, if T is unsized (e.g. str or [i32] or dyn Fn()), *const T is a wide pointer.

Did you mean the other way around? *const T for an undetermined T is not a type. Anyway, it can't happen due to the above reasons (and probably others).

3 Likes

No I mean, *const u8 is same as *const T, Both represent as raw memory location,

But what is "wide pointer" ?

*const T isn't a type unless T is known. If that was possible in Rust, it would be a higher-ranked (higher-kinded?) type, for<T> *const T or something.

In general, by the way, it's a breaking change to turn a type into an alias for another type. For example these would become overlapping implementations if A became an alias for B.

impl MyTrait for A { /* ... */ }
impl MyTrait for B { /* ... */ }

In this particular case, technical considerations aside, knowing the type is valuable information. People use various placeholder types like *const u8 or *const () or *const c_void when all they care about is the address, or when doing FFI, etc.

Certain types in Rust are dynamically sized (DST -- dynamically sized types). (I said "unsized" above, which was sloppy of me [1].) The size of DSTs is tracked at runtime (dynamically) instead of being known statically at compile time. There's not much you can do with DSTs directly in Rust currently, instead you're always dealing with some sort of pointer to a DST -- &str, Box<dyn Fn()>, *const [u32], something like that.

These pointers to DSTs are wide [2] pointers because they are twice the size of a normal pointer. They consist of a pointer to the memory, and a word of extra information. The extra information depends on the DST.

  • With [T] it's the number of T in the slice
  • With str it's the number of bytes in the str; conceptually a str is a [u8] underneath
  • With dyn Trait (including dyn Fn() etc), it's a pointer to a static vtable, which includes the size of the underlying type that implemented the trait, along with other data like function pointers for the methods

You can read more here and also here.


  1. when types with static sizes -- non-DSTs -- can coerce to DSTs, it's called an unsizing coercion ↩︎

  2. sometimes call fat ↩︎

7 Likes

Assuming T isn't a dynamically sized type (e.g. a *const dyn SomeTrait or *const [u8]), yes, they'll have exactly the same representation in memory.

However, that doesn't mean they are the same.

To point out the obvious, one points to a value of type T while the other points to a byte (which just so happens to be the first byte of the T value). That means when you try to dereference the first one you'll read/write a T while dereferencing the second will read/write a byte.

So while the value may not change when casting from *const T to *const u8, their meaning and the way you interact with the pointers will.

2 Likes