I ran into this a little while ago and thought it would be helpful to share a possible solution.

Imagine you have an enum that describes a set of possible branches, for each branch there is a type associated with it that you want to run through a function, for this example– serialize.

enum Var { One , Two , } #[derive(Serialize)] struct Foo ; #[derive(Serialize)] struct Bar ; fn write < W > ( var : Var , mut writer : W ) -> serde_json :: Result < () > where W : Write , { match var { Var :: One => serde_json :: to_writer ( & mut writer , & Foo ), Var :: Two => serde_json :: to_writer ( & mut writer , & Bar ), } }

Life is good. Then you realize that you actually needed to format these types in two different ways, one with to_writer and the other with to_writer_pretty . You could make a write and write_pretty function, but that feels dirty. The only thing that would be different in each implementation is the function from serde_json . Naively, it would look something like this:

fn write < W > ( var : Var , mut writer : W ) -> serde_json :: Result < () > where W : Write , { match var { Var :: One => serde_json :: to_writer ( & mut writer , & Foo ), Var :: Two => serde_json :: to_writer ( & mut writer , & Bar ), } } fn write_pretty < W > ( var : Var , mut writer : W ) -> serde_json :: Result < () > where W : Write , { match var { Var :: One => serde_json :: to_writer_pretty ( & mut writer , & Foo ), Var :: Two => serde_json :: to_writer_pretty ( & mut writer , & Bar ), } }

No problem, you think, you’ll parameterize the formatting function and pass it as a closure.

fn write < W , T , F > ( var : Var , mut writer : W , f : F ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized , F : Fn ( & mut W , & T ) -> serde_json :: Result < () > , { match var { Var :: One => f ( & mut writer , & Foo ), Var :: Two => f ( & mut writer , & Bar ), } }

But this is folly, write is declared to be valid for any T , but it’s passed a concrete T in the implementation (either Foo or Bar ). Indeed, this will generate an error.

error [ E0308 ] : mismatched types --> src/lib.rs:23:36 | 16 | fn write<W, T, F> ( var: Var, mut writer: W, f: F ) -> serde_json::Result< () > | - this type parameter ... 23 | Var::One = > f ( & mut writer, & Foo ) , | ^^^^ expected type parameter ` T ` , found struct ` Foo ` | = note: expected reference ` & T ` found reference ` & Foo ` = help: type parameters must be constrained to match other types = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters error [ E0308 ] : mismatched types --> src/lib.rs:24:36 | 16 | fn write<W, T, F> ( var: Var, mut writer: W, f: F ) -> serde_json::Result< () > | - this type parameter ... 24 | Var::Two = > f ( & mut writer, & Bar ) , | ^^^^ expected type parameter ` T ` , found struct ` Bar ` | = note: expected reference ` & T ` found reference ` & Bar ` = help: type parameters must be constrained to match other types = note: for more information, visit https://doc.rust-lang.org/book/ch10-02-traits.html#traits-as-parameters

This is where higher ranked types could come in. If you could declare F: for<T> Fn(&mut W, &T) -> serde_json::Result<()> . Basically, declaring that T is parameterized over the function F and not over write , then this would be valid. But such things are not allowed in Rust today.

How then to solve this problem?

Solutions

Type Erasure

You can usually ‘erase’ a type parameter with a trait object,

fn write < W , T , F > ( var : Var , mut writer : W , f : F ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized , F : Fn ( & mut W , & dyn Serialize ) -> serde_json :: Result < () > , { match var { Var :: One => f ( & mut writer , & Foo ), Var :: Two => f ( & mut writer , & Bar ), } }

The problem with this is that Serialize is not object safe. If a trait has generic methods like Serialize does, it can’t be turned into a trait object. I encourage you to read the link, it’s enlightening.

There are crates like erased_serde that will allow one to make a Box<dyn Serialize> .

Another Enum

You can make another enum representing the different parameters that could be passed to the writer. I don’t think you really gain a lot from this though and I’m not sure it’s much better than just having the two explicit write / write_pretty variants.

enum Foobar < 'a > { Foo ( & 'a Foo ), Bar ( & 'a Bar ) } fn write < W , T , F > ( var : Var , mut writer : W , f : F ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized , F : Fn ( & mut W , FooBar ) -> serde_json :: Result < () > , { match var { Var :: One => f ( & mut writer , Foo ( & Foo )), Var :: Two => f ( & mut writer , Bar ( & Bar )), } }

But then the closure has to handle the multiple variants also,

write ( var , writer , | writer , foobar | { match foobar { Foo ( foo ) => serde_json :: to_writer_pretty ( writer , foo ), Bar ( bar ) => serde_json :: to_writer_pretty ( writer , bar ), } }) // and later: write ( var , writer , | writer , foobar | { match foobar { Foo ( foo ) => serde_json :: to_writer ( writer , foo ), Bar ( bar ) => serde_json :: to_writer ( writer , bar ), } })

You want to do the same thing for each type. Is there an easier way? You don’t want to add a dependency, and you’re at the point now where this seems like an awful lot of work to try to abstract this bit of code. Maybe you should just copy-paste, make the modifications and be done with it?

Cheating Rank-2

As usual in Rust– traits to the rescue. It’s possible to get around this by creating a trait with the behaviour we’re looking for

trait Format { fn format < W , T > ( & self , writer : W , val : & T ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized ; } struct Ugly ; struct Pretty ; impl Format for Ugly { fn format < W , T > ( & self , writer : W , val : & T ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized , { serde_json :: to_writer ( writer , val ) } } impl Format for Pretty { fn format < W , T > ( & self , writer : W , val : & T ) -> serde_json :: Result < () > where W : Write , T : Serialize + ? Sized , { serde_json :: to_writer_pretty ( writer , val ) } }

Now, you can make write parameterized by this trait,

fn write < W , F > ( var : Var , mut writer : W , format : F ) -> serde_json :: Result < () > where W : Write , F : Format , { match var { Var :: One => format . format ( & mut writer , & Foo ), Var :: Two => format . format ( & mut writer , & Bar ), } }

Elsewhere, you can simply call the write function with,

write ( var , writer , Ugly ) ? ;

Because <T> is bounded over the format function and not the trait itself, it’s effectively the same thing as a rank-2 type ( for<T> Fn(T) ). I think this shows something interesting about type systems with traits or typeclasses; it’s sometimes valuable to create new types even if they hold no data, even if their only purpose is to abstract a bit of behaviour and allow you to attach it to a type.