Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Can somebody explain this line:

    n => Token::Operand(n.parse().unwrap()),
How does the compiler derive the type of n?


If you've never been exposed to a Hindley-Milner type system[1] it can seem a bit magical, but it essentially works by trying to figure out the types from the inside and out by inferring usage all the way to the top. The type of `n` however is `&str`, but I take it you mean the matching. `n.parse()` can be anything that implements `FromStr`, but `Token::Operand` can only take a `u32`, so it can immediately infer that the result of `n.parse().unwrap()` must be `u32` (`n.parse()` is a `Result<u32, Err>`).

[1]: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_sy...


Operand(u32) (see definition of Token), n will be parsed as a u32. See here: https://doc.rust-lang.org/std/primitive.str.html#method.pars...


We're doing a pattern match, so, this variable n has to be something that matches the entire value matched, its type will be identical to the type of the value matched, s a few lines earlier.

That value is an item from the iterator we got from calling split_whitespace() and split_whitespace() returns a SplitWhiteSpace, a custom iterator whose items are themselves sub-strings of the input string with (no surprise) no white space in them. In Rust's terminology these are &str, references to a string slice.

So, the type is &str


Aha. But what type does n.parse() have then, and how does the compiler derive it?


In this case the compiler actually first wants the type for a parameter to the function Token::Operand

That function is not shown, but it is included in the full source code which was linked. Well, technically we need to know that Rust says if there's a sum type Token::Operand which has an associated value, we can always call a function to make a Token::Operand with that value, and it just names this function Token::Operand too.

So, Token::Operand takes an i32, a 32-bit signed integer. The compiler knows we're eventually getting an i32 to call this function, if not our program isn't valid.

Which means n.parse().unwrap() has the type i32

We know n is an &str, the &str type has a generic function parse(), with the following signature:

    pub fn parse<F>(&self) -> Result<F, <F as FromStr>::Err> where F: FromStr
So the type you now care about, that of n.parse() has to be Result of some kind, and we're going to call Result::unwrap() on that, to get an i32

This can only work if the type F in that generic signature above is i32

Which means the new type you care about was Result<i32, ParseIntError> and the parse function called will be the one which makes Ok(i32) when presented an in-range integer.

Edited: Word-smithing, no significant change of meaning.


That function is returning a Vec<Token>, and so it knows the .collect() call needs to return a Vec<Token>, and so therefore the .map() function needs to return a Token. Therefore each match arm needs to return a Token too, so therefore the compiler selects the implementation of .parse() that returns Token.

I admit when I started rust, seeing calls to .parse() was one of the more confusing things I saw in rust code, because of how much it leans on type inference to be readable. In places like these, it's a bit more readable:

    let ip: IpAddr = ip_str.parse()?;
But when you see the .parse buried several levels deep and you have no idea what type it's trying to produce, it's a pain in the ass to read. This is why it's nice to use the turbo-fish syntax:

    let ip = ip_str.parse::<IpAddr>()?;
Since you can drop .parse::<IpAddr>()? anywhere to make the type explicit, especially when buried in type-inferred blocks like the code in TFA.


`n` is the same type as `s` from "match s" and 'n' is just `s` but renamed, if none of the previous conditions passed.

Because `match <exp>` could have contained an expression, you might need to handle a "catch all" case where you can refer to the result of that expression.

The code could have been `match s.doSomething() { ...`. The lines above what you have quoted just compare the result to a couple of a constants. If none are true, the line that you have quoted is equivalent to renaming the result of that expression to `n` and then handling that case.


maybe this isn't the question you meant to ask, but:

`n` has the same type as the input of the `match` block. In other words, it's a fallback case. (In this case, it's `&str`; the same as `"+"`, `"-"`, etc)

If you're wondering how `n.parse().unwrap()` has its type computed, well that part is because type inference is able to look at the definition of `Token::Operand(u32)` and discover that it's `u32`.

From my experience: The compiler can do this, as long as the first usage of the unknown-typed-thing gives it a type. If the first usage of it doesn't, then it won't try any harder to infer the type and it won't compile unless you add your own annotations on.


Might also be useful for me to link to the docs for `parse` [1] and to the trait `FromStr` [2] that it relies on:

[1]: https://doc.rust-lang.org/std/primitive.str.html#method.pars... [2]: https://doc.rust-lang.org/std/str/trait.FromStr.html


That's an i32 not a u32 - the operands are allowed to be say -1234 not only positive numbers apparently.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: