How to implement a trait for both T and Vec<T>

hi,

I would like to declare a trait to get inner buffer of a type as the below:

trait AsMutBuffer {
    unsafe fn as_mut_buffer(&mut self) -> &mut [u8];
}

impl<T> AsMutBuffer for T {
    unsafe fn as_mut_buffer(&mut self) -> &mut [u8] {
        let len = std::mem::size_of::<T>();
        let ptr = self as *mut _ as *mut u8;
        std::slice::from_raw_parts_mut(ptr, len)
    }
}

impl<T> AsMutBuffer for Vec<T> {
    unsafe fn as_mut_buffer(&mut self) -> &mut [u8] {
        let len = self.len() * std::mem::size_of::<T>();
        let ptr = self.as_mut_ptr();
        std::slice::from_raw_parts_mut(ptr, len)
    }
}

but stuck at the compile error

this is possible for c++ as Vec<T> is more specific on T.. but rust doesn't allow that..

How to achive the same result on rust??

You can't, without specialization (which is far from being stabilized, since it's unsound as-is). A type variable T matches all types, including Vec<T>.

4 Likes

Rust does not offer ways for “more specific” implementations to override “less specific” ones. There’s a long-time-unstable feature called “specialization” which aims to achieve this, but it’s hard to pull off in a satisfying and sound manner.


Regarding the specific code at hand, I cannot really imagine sound use-cases though where you couldn’t just either manually pick the appropriate one of these two unsafe functions, or write a more specific set of impls for those types that you actually do use, but feel free to educate about your use-case, in case that’s impossible there, and I’ll gladly point out any soundness issues I can find :slight_smile:

2 Likes

There's an existing implementation of this that you can reuse:

and it uses a Pod trait to make it safe.

2 Likes

Thanks. for my use case, there are many different C types ( or Vec types), I would like to read bytes from file and convert it to these types.
e.g

let mut c = CType::default();
file.read(<&mut c -> &mut [u8]>);

If it just concerns serialization and IO, serde is very powerful and supports efficient binary protocols like CBOR.

Thanks for the information,
Take a quick look on bytemuck, seems like Pod trait is need to be implemented for the casting types, which is not quite fit my need. Types I want to cast from bytes are defined by other crate, I can't implement Pod for those types :joy:

That's very likely unsound, unless the crate has specific documentation about a guaranteed layout for those types.

2 Likes

That is a very C-like risky design pattern that Rust tries hard to avoid. It can't work for arbitrary types, they must be compatible, so it is expected that they will opt in somehow:

  1. Layout of Rust-native types is not guaranteed. Order of struct fields may change, may even be randomized. Layout of tuples is undefined. Only #[repr(C)] types have a stable layout.

  2. Arbitrary types could contain pointers, which won't make any sense after sitting on disk. Some Rust types have forbidden bit patterns that are UB, e.g. you must never ever allow enum to contain a value out of range or store 2 in bool. Loading arbitrary bytes from disk doesn't let you control for this.

  3. To use this trait you first need to get a properly initialized instance of that type. You'll need to rely on another trait like Default to do this. It's not safe to cast arbitrary buffers, not even zeroed memory, to make an instance of an arbitrary Rust type. For reading you probably should use MaybeUninit<T> instead.

In Rust-idiomatic code, foreign types usually implement something like serde Deserialize, which then can be safely used with any representation. For serde there's bincode which is pretty fast and close to being a memory dump of a struct, but it guarantees to be safe and portable.

3 Likes