How to implement traits for a type containing a function type?

I have an example of a type which contains a configurable function with a default implementation. This compiles and runs fine as long as the [derive(PartialEq)] is commented out. If it's not commented out, I get errors:

type StringMunger = fn(&str) -> String;

fn default_string_munger(s: &str) -> String {
    format!("Hello, {}!", s)
}

#[derive(PartialEq)]
struct Thing {
    string_munger: StringMunger,
}

impl Thing {
    fn new() -> Self {
        Self{string_munger: default_string_munger}
    }
    
    fn munge(&mut self, s: &str) -> String {
        (self.string_munger)(s)
    }
}

fn main() {
    let mut thing = Thing::new();
    
    println!("{}", thing.munge("world"));
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0277]: `for<'r> fn(&'r str) -> std::string::String` doesn't implement `std::fmt::Debug`
 --> src/main.rs:9:5
  |
9 |     string_munger: StringMunger,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `for<'r> fn(&'r str) -> std::string::String` cannot be formatted using `{:?}` because it doesn't implement `std::fmt::Debug`
  |
  = help: the trait `std::fmt::Debug` is not implemented for `for<'r> fn(&'r str) -> std::string::String`
  = note: required because of the requirements on the impl of `std::fmt::Debug` for `&for<'r> fn(&'r str) -> std::string::String`
  = note: required for the cast to the object type `dyn std::fmt::Debug`

error[E0369]: binary operation `==` cannot be applied to type `for<'r> fn(&'r str) -> std::string::String`
 --> src/main.rs:9:5
  |
9 |     string_munger: StringMunger,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an implementation of `std::cmp::PartialEq` might be missing for `for<'r> fn(&'r str) -> std::string::String`

error[E0369]: binary operation `!=` cannot be applied to type `for<'r> fn(&'r str) -> std::string::String`
 --> src/main.rs:9:5
  |
9 |     string_munger: StringMunger,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an implementation of `std::cmp::PartialEq` might be missing for `for<'r> fn(&'r str) -> std::string::String`

error: aborting due to 3 previous errors

Some errors have detailed explanations: E0277, E0369.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

If I naïvely try to add

impl PartialEq for StringMunger {
    fn eq(&self, other: &Self) -> bool {
        false
    }
}

then I get a different error:

error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
  --> src/main.rs:12:1
   |
12 | impl PartialEq for StringMunger {
   | ^^^^^---------^^^^^------------
   | |    |             |
   | |    |             `for<'r> fn(&'r str) -> std::string::String` is not defined in the current crate
   | |    `for<'r> fn(&'r str) -> std::string::String` is not defined in the current crate
   | impl doesn't use only types from inside the current crate
   |
   = note: define and implement a trait or new type instead

Of course, I want to implement PartialEq, Debug etc. for Thing, but I can't derive them automagically because of the use of the function type. How do I achieve the ability to implement those traits for Thing as easily as possible?

Function pointer types are not defined in your crate, so you can't implement foreign traits for them. If you make a newtype struct, you can implement any traits for it:

struct StringMunger(fn(&str) -> String);

Or you can just implement the traits you want on Thing instead of deriving them. That should also work.

2 Likes

Neat - thanks. So that's a tuple struct - the function pointer would be accessed as .0 of a struct instance.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.