Skip to content

#[derive] Debug, PartialEq, Hash, etc. for any function pointers, regardless of type signature #54508

Closed
@fschutt

Description

@fschutt

Suppose you have a situation like this:

trait MyTrait { }

// This works:
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct WorkingFunctionPointer<T: MyTrait>(fn(T));

// This doesn't work:
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
struct NotWorkingFunctionPointer<T: MyTrait>(fn(&T));

https://play.rust-lang.org/?gist=d1bd43980abfb37197a8aaf84ed7b529&version=stable&mode=debug&edition=2015

You can implement this manually by writing something like this:

// #[derive(Debug, Clone, PartialEq, Hash, Eq)] for NotWorkingFunctionPointer<T>

impl<T: Layout> fmt::Debug for NotWorkingFunctionPointer<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "NotWorkingFunctionPointer(0x{:x})", self.0 as usize)
    }
}

impl<T: Layout> Clone for NotWorkingFunctionPointer<T> {
    fn clone(&self) -> Self {
        NotWorkingFunctionPointer(self.0.clone())
    }
}

impl<T: Layout> Hash for NotWorkingFunctionPointer<T> {
  fn hash<H>(&self, state: &mut H) where H: Hasher {
    state.write_usize(self.0 as usize);
  }
}

impl<T: Layout> PartialEq for NotWorkingFunctionPointer<T> {
  fn eq(&self, rhs: &Self) -> bool {
    self.0 as usize == rhs.0 as usize
  }
}

impl<T: Layout> Eq for NotWorkingFunctionPointer<T> { }
impl<T: Layout> Copy for NotWorkingFunctionPointer<T> { }

... but this is tedious to do and leads to a lot of boilerplaite code. Even worse, this is especially bad if you have a FunctionPointer<T> used in a struct like this:

// #[derive] won't work here, same error!
struct Something<T: MyTrait> {
    ptr: NotWorkingFunctionPointer<T>,
    // other fields for demonstration
    blah: Blah,
    foo: Foo,
    baz: Baz,
}

... because then #[derive] doesn't work on the Something struct! This means you have to copy-paste all over again:

// A manual #[derive(Debug, Clone, PartialEq, Hash, Eq)] for Something<T>

impl<T: Layout> fmt::Debug for Something<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Something { ptr: {:?}, blah: {:?}, foo: {:?}, baz: {:?} }", self.ptr, self.blah, self.foo, self.baz)
    }
}

impl<T: MyTrait> Clone for Something<T> {
    fn clone(&self) -> Self {
         Something {
             ptr: self.ptr.clone(),
             blah: self.blah.clone(),
             foo: self.foo.clone(),
             baz: self.baz.clone(),
         }
    }
}

impl<T: MyTrait> Clone for Something<T> {
    fn clone(&self) -> Self {
         Something {
             ptr: self.ptr.clone(),
             blah: self.blah.clone(),
             foo: self.foo.clone(),
             baz: self.baz.clone(),
         }
    }
}

impl<T: Layout> Hash for Something<T> {
  fn hash<H>(&self, state: &mut H) where H: Hasher {
    state.hash(self.ptr);
    state.hash(self.foo);
    state.hash(self.bar);
    state.hash(self.baz);
  }
}

impl<T: Layout> PartialEq for Something<T> {
  fn eq(&self, rhs: &Self) -> bool {
    self.ptr == rhs.ptr &&
    self.foo == rhs.foo &&
    self.bar == rhs.bar &&
    self.baz == rhs.baz &&
  }
}

impl<T: Layout> Eq for Something<T> { }
impl<T: Layout> Copy for Something<T> { }

.. and over and over and over again, for each struct that you wrap / use Something in. Nevermind that this is error-prone if you add a field to the Something struct, don't forget to update the hash() and fmt() functions! This leads to a whole bunch of code that I need to copy-paste because derive doesn't work.

The real-world code where I encountered this problem is:

https://github.com/maps4print/azul/blob/4f2ba2e6eebdd0718d1adb15aac34c643f0f94ca/src/dom.rs#L159-L228
https://github.com/maps4print/azul/blob/4f2ba2e6eebdd0718d1adb15aac34c643f0f94ca/src/dom.rs#L358-L394
https://github.com/maps4print/azul/blob/4f2ba2e6eebdd0718d1adb15aac34c643f0f94ca/src/dom.rs#L413-L452

It's just stupid, copy-pasted code and if possible, I'd like to get rid of it with derive, but right now I sadly can't. My manual code is just a workaround for now, and I'd like this to be properly fixed somehow. Thanks in advance for any help.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-feature-requestCategory: A feature request, i.e: not implemented / a PR.T-langRelevant to the language team

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions