Help making this statement a bit more intuitive?

The book of rust states shows an error as an example:

2 | let guess = "42".parse().expect("Not a number!");
| ^^^^^ ----- type must be known at this point

and the solution here is to use a number type for the guess variable:

let guess:u32 = "42".parse().expect("Not a number!");

What is confusing to me is that I expect u32 to be either a separate layer of type information (not code) or an argument to parse("u32").

I this case the :u32 does not seem just like a simple type annotation but a function doing some task. But in this case, it's confusing to which function it is related to, or where in the chain it acts.

So if one has more chaining

let guess:u32 = "42 ".trim().another().another1().parse().expect("Not a number!");

It becomes even harder.

I am a beginner, so likely there are several misconceptions, but would appreciate at least some starting points (I am reading the book already) or intuitive way to see how to understand the unsigned integer (:u32) type's role here.

The :u32 annotation helps the compiler deduce the types involved. Type inference affects behavior because certain functions (such as parse) are polymorphic, i.e. they work differently for different types.

If you want to avoid relying on type deduction you can pass the type parameter to parse explicitly:

 let guess = "42".parse::<u32>().expect("Not a number!");
6 Likes

Is there a best practice or recommendation for when to use which, or just personal taste?

You can also use the non-reversed function:

let guess = u32::from_str("42").expect("Not a number!");

I'd argue that it's personal taste. Generally you should use a consistent way within the same project, but other than that the difference is purely aesthetic.
I personally prefer the type annotation way when it comes to parsing, as showcased by The Book.

3 Likes

The book uses let name: type because that's similar to the syntax in other languages, so for people coming from TypeScript or Swift this may be the obvious way to specify the desired type.

That makes sense; imho the book style is fine as it is. It was just me finding the relation to parse confusing.

I find it nice when you have a function that uses the value, then the type annotation lives as just part of the regular function signature:

To me, there's a nice symmetry in how I don't provide a type annotation directly to n, as it is directly inferred the same as both extra and sum by their usage.

UPDATE: this is kinda off topic from original post, but just wanted to throw in another alternative

1 Like

Rust has "real" type inference in the sense that the compiler can deduce the types of expressions based on the context; type information can travel "backwards" or "top-down". Essentially, type inference means solving a system of equations. If there exists a unique solution (assignment of types), the type checking succeeds; if there is no solution, or there are multiple solutions, the compiler will bail out with an error.

For example, we know that in let 𝛼 = 𝛽, if 𝛼: T and 𝛽: U, then T = U. In English, the type of a binding equals the type of its initializing expression, and critically, being an equality, it works both ways! It's perfectly valid to deduce the type of the initializer based on the type of the binding.

So when we have a polymorphic function like str::parse:

pub fn parse<F: FromStr>(&self) -> Result<F, F::Err>

when the type checker encounters an expression like "42".parse(), it knows its type is Result<?0, <?0 as FromStr>::Err> for some new "unknown" type variable ?0: FromStr. If it can eventually deduce, based on the typing rules, that ?0 = T for some concrete known type T, it knows that the ambiguous parse::<?0>() call is actually parse::<T>() and everything is fine.

2 Likes