Is this a type inference bug? or quirk?

let me show the code first:

pub trait TryFromBuf: Sized {
	fn try_from_buf<B: AsRef<[u8]>>(b: B) -> Result<Self, ()>;
}

pub trait InternalTryInto<T: Sized>: Sized {
	fn try_into(self) -> Result<T, ()>;
}

impl<T, B> InternalTryInto<T> for B
where
	T: TryFromBuf,
	B: AsRef<[u8]>,
{
	fn try_into(self) -> Result<T, ()> {
		T::try_from_buf(self)
	}
}

fn main() {
    let b = &[4, 2];
    println!("{}", u16::from_le_bytes(b[0..2].try_into().unwrap()));
}

or test it on playground:

(can I just link to the playground or do I have to paste the code here?)

it emits this error:

error[E0034]: multiple applicable items in scope
  --> src/main.rs:21:47
   |
21 |     println!("{}", u16::from_le_bytes(b[0..2].try_into().unwrap()));
   |                                               ^^^^^^^^ multiple `try_into` found
   |
note: candidate #1 is defined in an impl of the trait `InternalTryInto` for the type `B`

this is a little surprising for me, since there's no impl TryFromBuf for [u8;2]. and we already knew std try_into works because rust type inference can match on return type (is my understanding wrong?).

Programming rust is mostly fun but sometimes feel like wrestling with the compiler :frowning:

BTW, this was a (failed) attempt of a workaround for rust not allowing implementing external trait generically, in this case:
impl<T: AsRef<u8>> std::convert::TryFrom<T> for MyStrcut
or:
impl<T: TryFromBuf> std::convert::TryFrom<&[u8]> for T

I have multiple internal structs that can be constructed from &[u8] or mmap and I wanted to generalize on that, and using the familiar .try_into() pattern, I settled by just renaming try_into to parse. maybe this is bad practice, I'm of course open to suggestions.

1 Like

You don't have to paste it. (I just use my best judgement; length is a factor.)

Method resolution has it's own algorithm that revolves around the receiver.

There's still a surprising aspect: [u8] doesn't implement InternalTryInto, but it still caused a conflict, which doesn't correspond to the documentation:

Then, for each candidate type T, search for a visible method with a receiver of that type in the following places:

  1. T’s inherent methods (methods implemented directly on T).
  2. Any of the methods provided by a visible trait implemented by T.

I believe that the actual mechanism is that where clauses are ignored, and it only considers if the implementing type in the implementation could apply. The implementations for TryInto and InternalTryInto are both uncovered blanket implementations (the implementing type is a generic T), so they could apply to everything.

In contrast if you had

impl<T, B> InternalTryInto<T> for Vec<B>

then it couldn't apply to [u8].

Unfortunately I don't have a citation on hand.

1 Like