Adding external crate breaks type inference

Recently I faced an issue where using a third-party library broke my code, which depended on type inference. I found the root cause and made a minimised example (full code):
main.rs:

fn _f(x: usize, y: i64) -> bool {
    use std::convert::TryInto;
    if let Ok(y) = y.try_into() {
        x == y
    } else {
        false
    }
}

fn main() {
    // Uncommenting this causes an error
    // use foo_lib::useful_func;
}

lib.rs from foo-lib crate:

pub fn useful_func() {}

mod private_mod {
    struct PrivateType;

    impl PartialEq<PrivateType> for usize {
        fn eq(&self, _other: &PrivateType) -> bool {
            unimplemented!();
        }
    }
}

If I uncomment the line in main.rs I get the following error:

error[E0282]: type annotations needed
 --> src/main.rs:3:15
  |
3 |     if let Ok(y) = y.try_into() {
  |               ^ cannot infer type

I have a few questions:

  1. In this particular example, why does type inference fail? Turning y into PrivateType will not work while usize must work.
  2. How do I shield from such trait impls., which break out of private modules and crate boundaries?
    2.1. Why does Rust consider inaccessible trait implementations when inferring types?
    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0db081f89615d07db4f0befbb835ab65
    It does not seem that a trait impl. could be used if its type parameter is private.
    https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=e44a3df199e9a31a874cd6e176d84cda
    (P.S. two links limit is annoying)
  3. If this behaviour is totally expected, does it mean that I should avoid making my code depend on type inference so that it does not get broken like this?
1 Like

This doesn't seem surprising to me. You should specify your types when you use things like TryFrom and Iterator::collect because they are so general that it is really easy for inference to break.

2 Likes

In general, you don't need to fear type inference, because for most of the code there's just one useful combination of types.

But there are a few open-ended traits like (Try)Into, PartialEq, and FromIterator that can connect anything to anything.

In your case it's even a combination of two of them together, so I'm surprised that this code has even compiled in the first place. Allowing such code without type annotations is probably the real bug here.

Instead of y.try_into() you can use i64::try_from(y) to make the type clear.

4 Likes

My thoughts exactly.

1 Like