Confusing compilation behavior involving AsFd

I'm attempting to make some changes to the termios crate.

The relevant code can be found in this playground:

That code triggers this compiler error:

error[E0507]: cannot move out of `*writer` which is behind a mutable reference
  --> src/lib.rs:24:20
   |
24 |         let _raw = writer.into_raw_mode()?;
   |                    ^^^^^^ --------------- `*writer` moved due to this method call
   |                    |
   |                    move occurs because `*writer` has type `W`, which does not implement the `Copy` trait
   |
note: `IntoRawMode::into_raw_mode` takes ownership of the receiver `self`, which moves `*writer`
  --> src/lib.rs:13:22

I understand why this error occurs - writer is an &mut W, and the code is attempting to move the W elsewhere, which is illegal.

But now, try changing each instance of AsTermHandle /*AsFd*/ to AsFd. Here is another playground where the changes have been made:

This time, it compiles successfully. Huh? Why does this succeed when the previous code failed?

Neither AsTermHandle nor AsFd are Copy or Clone, so that can't be the answer.

I believe the answer has something to do with the AsFd trait - if you look in the docs, you'll find it has the following implementations:

impl<T: AsFd + ?Sized> AsFd for &T
impl<T: AsFd + ?Sized> AsFd for &mut T

Apparently these impl's were introduced in version 1.63.0 as some kind of "IO safety" feature.

But I would like my AsTermHandle trait to work in the same position as AsFd, and unfortunately, it seems to be impossible to do:

impl<T: std::os::fd::AsFd> AsTermHandle for T { /*...*/ }
impl<T: AsTermHandle + ?Sized> AsTermHandle for &T { /*...*/ }
impl<T: AsTermHandle + ?Sized> AsTermHandle for &mut T { /*...*/ }

If I try the implement the impl's above, the compiler fails with "conflicting traits." For completeness, here is a playground with the conflicting traits error:

So at this point, I'm stuck without performing a fairly substantial refactor of the termion crate, probably breaking library compatibility if I want to proceed.

Is there a relatively simple way to fix the conflicting traits error?

That is indeed why it compiles -- you can pass in the &mut W instead of trying to pass in the W.[1]

They are definitely conflicting. Imagine some StructX: AsFd, and consider which implementation would be used for &StructX.

  • The second implementation applies, which presumably you understand
  • But &StructX: AsFd too, so the first implementation also applies

If you delete the reference implementations, you still get &T: AsTermHandle + IntoRawMode when T: AsFd. But this cannot be assumed to be the case when T: AsFd is not required -- perhaps someone implemented IntoRawMode directly for StructY but not &StructY, say.

So this would work:

impl<T: std::os::fd::AsFd> AsTermHandle for T {
    fn as_term_handle(&self) -> BorrowedTermHandle<'_> {
        todo!();
    }
}

pub trait TermRead {
    //                                     vvvvvv
    fn read_passwd<W: Write + AsTermHandle + AsFd>(&mut self, writer: &mut W) -> io::Result<Option<String>> {
        let _raw = writer.into_raw_mode()?;
        todo!();
    }
}

But if you don't want to require AsFd, you'll get the same "trying to move from &mut _" error as before.

There's currently no way to have all three of the overlapping blanket implementations.

You could perhaps use different bounds which reflect the abilities you need:[2]

    fn read_passwd<W: Write + AsTermHandle>(&mut self, writer: &mut W) -> io::Result<Option<String>>
    where
        // There may also be better choices for the exact bound
        for<'a> &'a mut W: IntoRawMode,

  1. you may find you need to pass &W instead in some cases ↩︎

  2. I didn't attempt to see if this is a breaking change to the crate ↩︎

1 Like

Thanks, I solved this issue by having read_passwd take a W parameter:

 fn read_passwd<W: Write + AsTermHandle>(&mut self, writer: W) -> io::Result<Option<String>>

Since AsTermHandle is implemented for all AsFd, and AsFd is implemented for File, &File, and &mut File, this makes it possible for read_passwd to accept all three...

The problem is, I would prefer if read_passwd did /not/ accept a File argument -- a reference should be mandatory -- but my code compiles now, so I'm happy.

1 Like

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.