"impl has stricter requirements than trait" even though function signature is identical

Hi, in my project I made a trait (CanWriteBox) that I want to implement on my struct. Even though the function signature of the write_box_of function is identical between the trait definition and the implementation, I still get a impl has stricter requirements than trait error.
Below is the code in question isolated:

use std::hash::Hash;
use std::io::{Seek, Write};
use std::marker::PhantomData;

pub trait WriteDomain: Sized {
    type Pointer;
    type Cat: Eq + Hash + Ord + Default + Clone;
    
    // ...
}

pub trait WriteCtx<Cat>
where
    Cat: Eq + Hash + Ord + Default + Clone,
{
    type Writer: Write + Seek;
    type InnerCtx<'a>: WriteCtx<Cat, Writer = Self::Writer> where Self: 'a;
    
    // ...
}

pub trait CanWriteBox: WriteDomain {
    fn write_box_of<W: WriteCtx<Self::Cat>>(
        &mut self,
        ctx: &mut W,
        write_content: impl FnOnce(&mut Self, &mut W::InnerCtx<'_>),
    );
}

struct FormatCgfx<C: Eq + Hash + Ord + Default + Clone> {
    _marker: PhantomData<C>,
}

impl<C: Eq + Hash + Ord + Default + Clone> WriteDomain for FormatCgfx<C> {
    type Pointer = u32;
    type Cat = C;
}

impl<C: Eq + Hash + Ord + Default + Clone> CanWriteBox for FormatCgfx<C> {
    fn write_box_of<W: WriteCtx<Self::Cat>>(
        &mut self,
        _ctx: &mut W,
        _write_content: impl FnOnce(&mut Self, &mut W::InnerCtx<'_>),
    ) {
        // ...
    }
}

(Playground)

I would appreciate help.

1 Like

The playground link doesn't work, but I am guessing it is a lifetime issue underneath... O.O

Oops sorry, I fixed the link

1 Like

The for<'a> bound is a standard fix for GAT related lifetime issues, but the real problem
here is with Self::Cat... Generic associated types/GAT's would probably require the next solver (new trait solver) for better support. You will need to be a bit hacky by making it a trait generic parameter instead of relying on Self::AssociatedType.

use std::hash::Hash;
use std::io::{Seek, Write};
use std::marker::PhantomData;

pub trait WriteDomain: Sized {
    type Pointer;
    type Cat: Eq + Hash + Ord + Default + Clone;

    // ...
}

pub trait WriteCtx<Cat>
where
    Cat: Eq + Hash + Ord + Default + Clone,
{
    type Writer: Write + Seek;
    type InnerCtx<'a>: WriteCtx<Cat, Writer = Self::Writer>
    where
        Self: 'a;

    // ...
}

pub trait CanWriteBox<Cat>: WriteDomain<Cat = Cat>
where
    Cat: Eq + Hash + Ord + Default + Clone,
{
    fn write_box_of<W, F>(&mut self, ctx: &mut W, write_content: F)
    where
        W: WriteCtx<Cat>,
        F: for<'a> FnOnce(&mut Self, &mut W::InnerCtx<'a>);
}

struct FormatCgfx<C: Eq + Hash + Ord + Default + Clone> {
    _marker: PhantomData<C>,
}

impl<C: Eq + Hash + Ord + Default + Clone> WriteDomain for FormatCgfx<C> {
    type Pointer = u32;
    type Cat = C;
}

impl<C: Eq + Hash + Ord + Default + Clone> CanWriteBox<C> for FormatCgfx<C> {
    fn write_box_of<W, F>(&mut self, _ctx: &mut W, _write_content: F)
    where
        W: WriteCtx<C>,
        F: for<'a> FnOnce(&mut Self, &mut W::InnerCtx<'a>),
    {
        // ...
    }
}

NOTE: Actually, try running with this flag and check, your original code with the nightly toolchain MIGHT run, as it seems logically okay to me: RUSTFLAGS="-Znext-solver" cargo +nightly

EDIT: Checked on my side! Your original code actually compiles with the next-solver :smiley:

1 Like

Oh, interesting. The fix is a bit unfortunate but acceptable, but that this works on the next solver is good to know

Another workaround could be to locally introduce some extra Self: WriteDomain bound to prevent Self::Cat from simplifying to C. Whether this is workable, or you can get away with not having the same Self::Cat to C simplification happening in the method body, either. For users of the impl on the other hand, Self: WriteDomain should not pose any additional restrictions (due to the generic impl<C: HeapCategory> WriteDomain for FormatCgfx<C>). I have only tested it works insofar as what you have in your github repo still compiles with this change:

diff --git a/src/main.rs b/src/main.rs
index fa32eb9..14eb8b9 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -143,8 +143,11 @@ impl<C: HeapCategory> WriteDomain for FormatCgfx<C> {
     }
 }
 
-impl<C: HeapCategory> CanWriteBox for FormatCgfx<C> {
-    fn write_box_of<W: WriteCtx<C>>(
+impl<C: HeapCategory> CanWriteBox for FormatCgfx<C> 
+where
+    Self: WriteDomain
+{
+    fn write_box_of<W: WriteCtx<Self::Cat>>(
         &mut self,
         ctx: &mut W,
         write_content: impl FnOnce(&mut Self, &mut W::InnerCtx<'_>) -> Result<()>,
@@ -157,8 +160,11 @@ impl<C: HeapCategory> CanWriteBox for FormatCgfx<C> {
     }
 }
 
-impl<C: HeapCategory> CanWriteSlice for FormatCgfx<C> {
-    fn write_slice_of<T: 'static, W: WriteCtx<C>>(
+impl<C: HeapCategory> CanWriteSlice for FormatCgfx<C> 
+where
+    Self: WriteDomain
+{
+    fn write_slice_of<T: 'static, W: WriteCtx<Self::Cat>>(
         &mut self,
         ctx: &mut W,
         values: &[T],
@@ -176,18 +182,27 @@ impl<C: HeapCategory> CanWriteSlice for FormatCgfx<C> {
     }
 }
 
-impl<C: HeapCategory> CanWrite<str> for FormatCgfx<C> {
-    fn write(&mut self, ctx: &mut impl WriteCtx<C>, value: &str) -> Result<()> {
-        Self::write_str(ctx, value)
+impl<C: HeapCategory> CanWrite<str> for FormatCgfx<C> 
+where
+    Self: WriteDomain
+{
+    fn write(&mut self, ctx: &mut impl WriteCtx<Self::Cat>, value: &str) -> Result<()> {
+        FormatCgfx::write_str(ctx, value)
     }
 }
-impl<C: HeapCategory> CanWrite<String> for FormatCgfx<C> {
-    fn write(&mut self, ctx: &mut impl WriteCtx<C>, value: &String) -> Result<()> {
-        Self::write_str(ctx, value)
+impl<C: HeapCategory> CanWrite<String> for FormatCgfx<C> 
+where
+    Self: WriteDomain
+{
+    fn write(&mut self, ctx: &mut impl WriteCtx<Self::Cat>, value: &String) -> Result<()> {
+        FormatCgfx::write_str(ctx, value)
     }
 }
-impl<C: HeapCategory> CanWrite<Pointer> for FormatCgfx<C> {
-    fn write(&mut self, ctx: &mut impl WriteCtx<C>, value: &Pointer) -> Result<()> {
+impl<C: HeapCategory> CanWrite<Pointer> for FormatCgfx<C> 
+where
+    Self: WriteDomain
+{
+    fn write(&mut self, ctx: &mut impl WriteCtx<Self::Cat>, value: &Pointer) -> Result<()> {
         Self::write_relative_ptr(ctx.cur_writer(), *value)
     }
 }

(this is of course happening on top of a version of the code that already turned the Cat parameter from associated type to type parameter in WriteCtx, but doesn’t do the other changes of the latest commit)

1 Like

interesting that this works, i might look into that. turning all of the associated types to type parameters was annoying and if i can revert that, i'll be glad