How to make impl of a specific type override impl of trait?

playground

struct Writer<F> {
    f: F
}

impl<F> Writer<F> {
    fn write_end(&mut self) -> std::io::Result<()> {
        // write something
        Ok(())
    }

    fn close(&mut self) -> std::io::Result<()> {
        self.write_end()?;
        Ok(())
    }
}

impl Writer<std::fs::File> {
    fn close(&mut self) -> std::io::Result<()> {
        self.write_end()?;
        self.f.sync_all()?;
        Ok(())
    }
}
   Compiling playground v0.0.1 (/playground)
error[E0592]: duplicate definitions with name `close`
  --> src/main.rs:11:5
   |
11 |     fn close(&mut self) -> std::io::Result<()> {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `close`
...
18 |     fn close(&mut self) -> std::io::Result<()> {
   |     ------------------------------------------ other definition for `close`

For more information about this error, try `rustc --explain E0592`.
error: could not compile `playground` due to previous error

There are no traits involved in your code. The impl<F> Writer<F> is a generic impl block, but not a trait implementation. Overriding the generic one with a specific one is not possible in general, since such a feature would amount to doing a version of “specialization”, a language feature that has been unstable for more than 8 for good reasons, as it’s awefully hard to design in a way that’s sufficiently general, yet doesn’t have soundness issues.

If the generic argument were to be restricted to the F: 'static case, a workaround of using the std::any API would be possible. More generally, the case at hand should also be sound, but it isn’t really supported at all by the compiler. The only downside to supporting such a case is very niche semver-comparibility concerns[1]. I’m also aware of a dirty workaround using unsafe code and abusing existing specialization in the standard library… but that’s not something one should use in real code, IMO.[2]


  1. something like: if std::fs::File were to defaulted type argument containing lifetimes; I wrote something about this in a thread I can’t find at the moment ↩︎

  2. I believe I’ve also written something about that in a thread I can’t find all that quickly now. ↩︎

It does not work with trait impls too.

struct Writer<F> {
    f: F
}

trait Close {
    fn close(&mut self) -> std::io::Result<()>;
}

impl<F> Writer<F> {
    fn write_end(&mut self) -> std::io::Result<()> {
        // write something
        Ok(())
    }
}

impl<F> Close for Writer<F> {
    fn close(&mut self) -> std::io::Result<()> {
        self.write_end()?;
        Ok(())
    }
}

impl Close for Writer<std::fs::File> {
    fn close(&mut self) -> std::io::Result<()> {
        self.write_end()?;
        self.f.sync_all()?;
        Ok(())
    }
}
   Compiling playground v0.0.1 (/playground)
error[E0119]: conflicting implementations of trait `Close` for type `Writer<std::fs::File>`
  --> src/main.rs:23:1
   |
16 | impl<F> Close for Writer<F> {
   | --------------------------- first implementation here
...
23 | impl Close for Writer<std::fs::File> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Writer<std::fs::File>`

For more information about this error, try `rustc --explain E0119`.
error: could not compile `playground` due to previous error

playground

Of course it doesn’t work with traits either. It’s not possible in any way shape or form. If it was possible, it would fall under

doing a version of “specialization”

which stable Rust does not offer in any shape or form beyond the cases where Any can be used. As I also explained above, Any can not be used to cover exactly your case either, but only the more limited case of restricting F with a 'static bound.


I only mentioned that there are no traits involved in your code, since you titled the topic as if traits were involved :wink:

You can use traits to do something like overriding, though, which is a default implemention:

trait OnClose {
  fn on_close(&mut self) -> std::io::Result<()> {
    Ok(())
  }
}

impl OnClose for Vec<u8> {}

impl OnClose for File {
  fn on_close(&mut self) -> std::io::Result<()> {
    self.sync_all()
  }
}

impl<F> Writer<F> where F: OnClose {
    ...

    fn close(&mut self) -> std::io::Result<()> {
        self.write_end()?;
        self.f.on_close()?;
        Ok(())
    }
}

It's not perfect, either you or your users need to explicitly implement your trait, even if it's trivial, like here for Vec, which means they are likely to run into the orphan rule, which in short means they can't implement someone else's trait on someone else's type.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.