1

I'm writing a wrapper for a C library and I'm stuck writing tons of types like CVecOf<anything>:

#[repr(C)]
pub struct CVecOfPoint {
    pub array: *mut Point2i,
    pub size: usize,
}

impl CVecOfPoint {
    pub fn rustify(&self) -> Vec<Point2i> {
        (0..self.size)
            .map(|i| unsafe { *(self.array.offset(i as isize)) })
            .collect::<Vec<_>>()
    }
}

#[repr(C)]
pub struct CVecOfPoints {
    pub array: *mut CVecOfPoint,
    pub size: usize,
}

impl CVecOfPoints {
    pub fn rustify(&self) -> Vec<Vec<Point2i>> {
        (0..self.size)
            .map(|i| unsafe {
                let vec = &*self.array.offset(i as isize);
                vec.rustify()
            })
            .collect::<Vec<_>>()
    }
}

pub struct CVecOfPointsOfPoints;
pub struct CVecOfPointsOfPointsOfPoints; 
pub struct CVecOfPointsOfPointsOfPointsOfPoints;

I'd like to write just CVec<T> with following mapping rules:

rustify :=
   T -> T
   CVec<T> -> Vec<T>

Thus CVecOfPointsOfPointsOfPointsOfPoints is just CVec<CVec<CVec<CVec<Cvec<Point>>>>>.

Thanks to @red75prime, I have written the following, but it requires an unstable feature:

#![feature(specialization)]
#![deny(trivial_casts)]

use std::fmt::Debug;
use std::mem;

#[repr(C)]
#[derive(Debug)]
pub struct CVec<T: Sized> {
    array: *mut T,
    size: usize,
}

unsafe fn unpack_unsafe<T, R>(v: &CVec<T>) -> Vec<R> {
    (0..v.size)
        .map(|i| mem::transmute_copy(&*v.array.offset(i as isize)))
        .collect()
}

pub fn unpack<T, U, F>(v: &CVec<T>, mut f: F) -> Vec<U>
where
    F: FnMut(&T) -> U,
{
    (0..v.size)
        .map(|i| unsafe { f(&*v.array.offset(i as isize)) })
        .collect()
}

trait Unpack {
    type R: Debug;
    fn unpack(&self) -> Vec<Self::R>;
}

impl<T: Debug> Unpack for CVec<T> {
    default type R = T;
    default fn unpack(&self) -> Vec<Self::R> {
        unsafe { unpack_unsafe(self) }
    }
}

impl<T: Unpack + Debug> Unpack for CVec<T> {
    type R = Vec<T::R>;
    fn unpack(&self) -> Vec<Self::R> {
        unpack(self, |v| v.unpack())
    }
}

fn main() {
    let mut vs = [1, 2, 3];
    let mut v1 = CVec {
        array: vs.as_mut_ptr(),
        size: vs.len(),
    };
    let mut v2 = CVec {
        array: &mut v1 as *mut _,
        size: 1,
    };
    let mut v3 = CVec {
        array: &mut v2 as *mut _,
        size: 1,
    };
    let v4 = CVec {
        array: &mut v3 as *mut _,
        size: 1,
    };
    let v = v4.unpack();
    println!("{:?}", v);

    let ptr: *mut () = &mut v3 as *mut _ as *mut _;
}

Is it possible to rewrite it with the stable compiler?

Important note: CVec<T> implements Drop because it must free allocated array memory so it cannot be Copy.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
  • Do you want a solution for *stable*? – VP. Jan 07 '18 at 09:39
  • @VictorPolevoy if possible. However, nighly is better than nothing. – Alex Zhukovskiy Jan 07 '18 at 09:40
  • Now we have nightly solution, Still waiting if someone has a solution for stable compiler. – Alex Zhukovskiy Jan 07 '18 at 17:18
  • You may want to read [How do I decide when to mark a trait as unsafe?](https://stackoverflow.com/q/31628572/3650362) Another possibility is that neither `trait Unpack` nor `fn unpack(&self)` is unsafe, but in fact `CVec` exposes a safe interface and *creating a `CVec`* is the only unsafe part (being analogous to `Vec::from_raw_parts`). Just a thought – trent Jan 09 '18 at 16:12
  • @trentcl thank you for a comment! However, vector is creating via `Default` trait which basically use `::std::ptr::null_mut::()` which is safe, and then is populated via FFI call, because for now all usages of these types are of `output variable` type. But your consideration are really valuable. However, if I had to populate it on Rust side i'd rather create a safe constructor that takes `Vec`, which is also safe, I guess :) – Alex Zhukovskiy Jan 09 '18 at 16:38
  • Then *populating* a `CVec` is the only unsafe part. My point is, do you want `CVec` to constitute a guarantee, like "when `array` is non-null, it always points to an array of `size` elements"? If not, you can't safely implement `Drop`, but if so, `unpack` doesn't need to be `unsafe` (when `array` is null, `unpack` could return an empty `Vec`). – trent Jan 09 '18 at 17:01
  • @trentcl let's move our discussion to the gist. You can then provide your arguments. I'm quite confused what are you talking about: https://gist.github.com/Pzixel/76ab59e545ac3c06fca752322b5aec82 . The only unsafe here is `unpack` func itself, because it's using `transmute`. – Alex Zhukovskiy Jan 09 '18 at 17:03
  • I can't get involved in a long discussion right now, but I'll try to respond later today. – trent Jan 09 '18 at 17:12

1 Answers1

5

In stable Rust implementations of traits cannot intersect and we can't use negative trait bounds. It makes impossible to use straightforward implementation like:

impl<T: Copy> Unpack for CVec<T> { // copies elements  } 
impl<T: Unpack> Unpack for CVec<T> { // calls `unpack` for elements }

but we can modify the trait and use the fact that CVec doesn't implement Copy.

The code below is sufficiently self-explanatory, I think.

#[repr(C)]
#[derive(Debug, Clone)]
pub struct CVec<T: Sized> {
    array: *mut T,
    size: usize,
}

// Unsafe because CVec is not guaranteed to contain valid pointer and size
unsafe fn unpack<T, U, F>(v: &CVec<T>, mut f: F) -> Vec<U>
where
    F: FnMut(&T) -> U,
{
    (0..v.size)
        .map(|i| f(&*v.array.offset(i as isize)))
        .collect()
}

trait Unpack {
    type Out;
    unsafe fn unpack(&self) -> Self::Out;
}

impl<T: Unpack> Unpack for CVec<T> {
    type Out = Vec<T::Out>;
    unsafe fn unpack(&self) -> Self::Out {
        unpack(self, |e| e.unpack())
    }
}

impl<T: Copy> Unpack for T {
    type Out = T;
    unsafe fn unpack(&self) -> Self::Out {
        *self
    }
}

Playground

Alex Zhukovskiy
  • 9,565
  • 11
  • 75
  • 151
red75prime
  • 3,733
  • 1
  • 16
  • 22