Are there any caveats to unspecified type?

fn main() {
  let mut v = Vec::new();
  let s = String::from("Hello ");
  v.push(s);
  v[0].push_str("world");
  println!("original: {}", s);// comment and compiles
  println!("new: {}", v[0]);
}

In this snippet from book, s is moved on push. But I thought this code wouldn't compile because Vec does not have a specified type, and isn't inferred from ::new().

So the compiler first accepts it can be anything, and then fills up that info? Is this a safe pattern to use?

I thought this code wouldn't compile because Vec does not have a specified type, and isn't inferred from ::new() .

The type becomes constrained due to the call to v.push(s); That's type inference too.

It is completely fine in this case, but there are reasons you might choose to put in a type anyway:

  • There are cases involving trait functions where a program can become ambiguous if more trait implementations are added. There are no trait functions involved in this particular inference (push is an inherent method of Vec), so this cannot fail in that way.

  • If let mut v is far away from v.push() then it might make sense to add the explicit type for readability, even if it is not necessary.

  • Writing explicit types can also help you get better error messages when trying to modify the code, because it tells the compiler what the type should be, and thus the compiler has more information about which line to blame when there is a type mismatch.

7 Likes

You wrote so many fine details, but neglected to say the most important, for a newbie, thing: if it compiles it works.

The rest of the explanation explains why it may refuse to compile, in some cases, and why specifying type explicitly may be a good idea even if it compiles and works, otherwise.

1 Like

rust type inference is more like how funcitonal programming languages work, it's very different from what's called "deduction" in C++.

when a type is not known yet, the compiler just put an inference variable in place, and later try to figure out all the variables according to the inference rules, or a diagnostic is reported when it wasn't able to.

a good anolog is solving math problems using algebra equations: you use variables to write out equations according to the constraints first, and then try to solve the equations, and it is an error if some equations are in conflicts or there's not enough equations.

3 Likes

This needs clarifying IMO.

If the type checker doesn't error, the you're guaranteed to have no type system errors, but it doesn't guarantee that the program actually works/is correct.

Many beginners in Rust come away with the feeling that if their program compiles, it must be 100% correct (because of the "strict" compiler), but that is not actually true. Your program is free from type errors and is sound[1], but it may have logical bugs, that the compiler's static analysis is not enough for.


  1. if you haven't used unsafe â†Šī¸Ž

2 Likes

I strongly agree in general! But I think the "it works" part here applies specifically to the fact that the correct type is inferred, or there is a compiler error -- whether this works was the question in the OP.

1 Like

Yes, I do think @khimru meant that, but I wanted to clarfy to the OP that that's only what they meant and not more.

1 Like

Well, if you implement addition as x - y then Rust wouldn't save you. It's kinda impossible to have language that makes it impossible to write incorrect code.

But as for types inference – it either works, or compiler complains and you add more info to make it work, that's the important part of the whole story.