mcarton / rust-derivative

A set of alternative `derive` attributes for Rust
Apache License 2.0
420 stars 46 forks source link

Deref support. #99

Open cheako opened 3 years ago

cheako commented 3 years ago

Describe the bug The Rust Pattern of wrapping one type in another was suggested to me as a solution of working with reference counting.

struct RcA(a-sys::A);

pub struct A(Rc<RcA>);

The Deref trait would remove .0 from all over the place. The point is to implement Drop for RcA such that the destructor for a-sys::A would be called. My target is ash/Vulkan.

A real world example: Much of the boilerplate is hidden inside macros, so only the unique parts are shown.

impl<T> Device<T> {
    pub fn new(
        instance: Instance<T>,
        physical_device: vk::PhysicalDevice,
        create_info: &vk::DeviceCreateInfo,
        user: T,
    ) -> Result<Self> {
        let inner = unsafe { instance.create_device(physical_device, create_info, None) }
            .context(VkCreateDevice {})?;
        let acceleration_structure_fn = AccelerationStructure::new(&**instance, &inner);
        let ray_tracing_fn = RayTracingPipeline::new(&**instance, &inner);
        Ok(Self(Rc::new(RcDevice {
            inner,
            instance,
            acceleration_structure_fn,
            ray_tracing_fn,
            user,
        })))
    }
}

pub struct RcDevice<T> {
    pub inner: VkDevice,
    pub instance: Instance<T>,
    pub acceleration_structure_fn: AccelerationStructure,
    pub ray_tracing_fn: RayTracingPipeline,
    pub user: T,
}

impl<T> Deref for RcDevice<T> {
    type Target = VkDevice;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl<T> Drop for RcDevice<T> {
    fn drop(&mut self) {
        unsafe {
            self.inner.destroy_device(None);
        };
    }
}

impl<T> Fence<T> {
    pub fn new(device: Device<T>, create_info: &vk::FenceCreateInfo, user: T) -> Result<Self> {
        Ok(Self(Rc::new(RcFence {
            inner: unsafe { device.create_fence(create_info, None) }.context(VkCreateFence {})?,
            device,
            user,
        })))
    }
}

pub struct RcFence<T> {
    pub inner: vk::Fence,
    pub device: Device<T>,
    pub user: T,
}

impl<T> Drop for RcFence<T> {
    fn drop(&mut self) {
        unsafe {
            self.device.inner.destroy_fence(**self, None);
        };
    }
}

Fence and CommandBuffer https://gitlab.com/cheako/ash-tray-rs/-/blob/c9a1ea4db239fca214f0cea09030096fc0390c3b/src/vk_helper.rs#L598-925 are two extremes and DescriptoSet is a complex example as well.

Expected behavior

    macro_rules! vk_subinner_types {
        ($($t:ident)*) => ($(
            concat_idents::concat_idents! (RcName = Rc, $t {
                impl<T> Deref for RcName<T> {
                    type Target = vk::$t;

                    fn deref(&self) -> &Self::Target {
                        &self.inner
                    }
                }
            });
        )*)
    }

    macro_rules! vk_inner_types {
        ($($t:ident)*) => ($(
            concat_idents::concat_idents! (RcName = Rc, $t {
                impl<T> Deref for $t<T> {
                    type Target = RcName<T>;

                    fn deref(&self) -> &Self::Target {
                        &self.inner
                    }
                }
            });
        )*)
    }

Most of the types(notably Device and Fence from above) are defined with this macro:

macro_rules! vk_tuple_types {
    ($($t:ident)*) => ($(
        concat_idents::concat_idents! (RcName = Rc, $t {
            #[derive(Derivative)]
            #[derivative(Clone(bound=""))]
            pub struct $t<T> (pub Rc<RcName<T>>);
            impl<T> Deref for $t<T> {
                type Target = RcName<T>;

                fn deref(&self) -> &Self::Target {
                    &self.0
                }
            }
        });
    )*)
}

A keen eye would have noticed that Device has a one-of Deref, actually two types like that. It's because "use ash::Device as VkDevice" != "use ash::vk; vk::Device"

Version (please complete the following information):

rustup --version
cargo --version
rustc --version
$ exit
rustup 1.24.3 (ce5817a94 2021-05-31)
info: This is the version for the rustup toolchain manager, not the rustc compiler.
info: The currently active `rustc` version is `rustc 1.56.0-nightly (ad02dc46b 2021-08-26)`
cargo 1.56.0-nightly (e96bdb0c3 2021-08-17)
rustc 1.56.0-nightly (ad02dc46b 2021-08-26)

Additional context I also do a lot of testing with CI and it's obviously not easy to run the above --version script. Though I need that information as well, so if you look at https://gitlab.com/cheako/ash-tray-rs/-/pipelines and https://gitlab.com/cheako/hazel-rs/-/pipelines you'll get more examples of version info and resulting errors. Thought I don't believe you'll find errors there, even for the fixed errors because generic is not Clone where I don't think I pushed any.

Also note that I'd love any criticism of this code, it often keeps me up at night wondering if this code is any good... And now I can add wondering if it's too much Golf.