Has anyone been bitten by type inference in Rust?


#1

I recently saw a discussion about the “overuse” of the var keyword in C# and had to do a double take. It’s been a long time since I’ve heard people worrying about type inference, and I’d kind of forgotten it was controversial in places.

So my question is, in Rust specifically, has anyone ever encountered a bug caused by/obscured by type inference?


#2

Interesting question. Type inference is fundamentally different from var/auto in that there are such things as “type inference” errors. The most useful cases of type inference are where it actually works backwards:

  • Consider a function like fn random<T: Random>() -> T;. At the point where you call this function, the type of the return value is not yet known, even to the compiler!
  • Reading forward, the compiler finds where the value is used (e.g. maybe you give it to a function that takes an f64), and deduces whatever information it can about the type.
  • At the end, if the type is still not fully known, it fails with a type inference error. (there is no equivalent to this in C++/C#!)

I can’t personally think of any cases where I was bit by it. Usually, I get a type error or inference error long before it would compile and do the wrong thing.

But that doesn’t mean it never happened; this could simply be confirmation bias. Or perhaps my mind is just throwing away perfectly good examples on the basis that they’re “not good code style.” (which would of course be silly since obviously this is the reason why it’s bad style! But I can’t help what my mind blocks out.)


I can contrive at least one example, though. Rust sometimes “infers” a type based solely on looking at where bounds, disregarding how it is used. If you write a function that is parametric in the type (i.e. doesn’t constrain it), it’s possible that rust could still choose a type for you by looking at the where bounds.

E.g. if you had some T: Default + Debug and wrote println!("{:?}", Default::default());, it might assume the desired type is T (which could be wrong) instead of throwing an inference error.


#3

Bug in my code - never. The only issues I had is that in some cases error messages can be confusing (Foo<_, > cannot be assigned to Foo<, _> - what?) and you may not know what type you’ll get because you are not familiar with some library internals to know all intermediates. I can imagine that expecting T1 and getting T2 when both implement the same trait could cause a problem somewhere, but I haven’t encounter that so far.


#4

I dunno if this is technically “type inference”, but I was recently bitten by the fact that Result will automatically deref (?) into FutureResult. I was working on some stuff with old-style tokio_core timeouts. I was creating a new timeout and using it in a loop_fn to delay between iterations. But it wasn’t actually producing a delay.

Turns out I didn’t notice that Timeout::new returns a Result<Timeout> and not just a Timeout and because I was expecting the Item type to just be (), the closure in the following .and_then() was just taking _ as a parameter so I had nothing to tell me that the type was wrong.


#5

It does cause breakage when new additions to the standard library make existing programs ambiguous. When there’s only one type that implements a trait you’re using, it compiles. When another matching type is added, you need to add explicit type annotation.

But it’s just a compile-time type error. I haven’t seen it cause bugs.


#6

This is likely an example (discussed today) of where type inference can lead someone astray: