I found a function with this signature and it confused me: fn read_uptime() -> Result<u64>. When I read about Result it seemed straight forward. It is an Enum that returns Ok or Err. As seen here: std::result - Rust
How can a function return a Result without any mention of the error? I found this example here: Handle Error Variants - Rust Cookbook. I've seen it in other places as well out in the wild but I don't understand this example in particular.
When we use the read_uptime function in main() how can it return anything other than an i64? I see the match arm for the error but clearly we don't have an error in the function signature.
It's common for libraries to make a typedef of Result with their Error type baked in to reduce repetition. You can even find an example of in the Standard Library's fmt module (std::fmt::Result)
To add to @spunit262's answer, you can tell the exact type of a function by clicking on it in the rustdoc.
For instance, at first glance std::fs::read appears to return Result<Vec<u8>> with only one type. But if you click on the "Result" in the documentation, you get to Result in std::io - Rust (std::io::Result's doc page) and can see that it's a type defined as:
type Result<T> = Result<T, Error>;
Clicking on the second Result in the doc page will bring you back to Result in std::result - Rust - the "real" Result type. (And clicking on Error brings you to std::io::Error)
So, if you were to fully expand it, std::fs::read's signature would read:
That '?' is a short hand for doing a match on the enum. It will return immediately from the function if the read fails, with an Error value in the Result enum.
If there is no error reading the file the function proceeds to return an 'Ok' as the Result enum.
The function does not return a i64 it returns an enum which may have a value which is an i64.
Ah Ok. So I've already been using this with Option. I guess I didn't connect the dots on only specifying the Type that could be returned if Ok().
So we could return some type of Error with Result but haven't explicitly said which type of Error. We have declared that the Ok result will contain an i64 type variable though.
No, not really. The implicitly in-scope std::result::Result is shadowed by the typedef, so after using the typedef, the bare name Result<i64> will refer to a type that is an alias for std::result::Result<i64, SomeErrorType>.
Result isn't special and the language doesn't assume anything about either of its type parameters. It's only that there has been a bit of "rewriting of history", so to say, using the typedef.
Ok that makes more sense. If I think of it as an alias that helps create a mental model. Will research how this typedef is done for further understanding.
It literally is just an alias. It's pretty similar to shadowing a variable name only for types. So it's less special than it might appear. For example, with your own types you can do:
mod mymodule {
pub struct MyType<T, U> {
pub foo: T,
pub bar: U,
}
}
type MyType<T> = mymodule::MyType<T, u16>;
Here the first MyType takes two generic types. The second MyType aliases the first but fills in one of the types for you so it only needs to take one itself. It doesn't matter that they have the same name because they are declared in different scopes.