book icon indicating copy to clipboard operation
book copied to clipboard

Diverging explanations of why types and lifetimes must be declared in function signatures

Open jruderman opened this issue 4 years ago • 0 comments

  • [x] I have checked the latest main branch to see if this has already been fixed
  • [x] I have searched existing issues and pull requests for duplicates

URL to the section(s) of the book with this problem:

https://doc.rust-lang.org/book/ch03-03-how-functions-work.html:

In function signatures, you must declare the type of each parameter. This is a deliberate decision in Rust’s design: requiring type annotations in function definitions means the compiler almost never needs you to use them elsewhere in the code to figure out what type you mean.

https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html:

The borrow checker can’t determine this either, because it doesn’t know how the lifetimes of x and y relate to the lifetime of the return value.

When annotating lifetimes in functions, the annotations go in the function signature, not in the function body. Rust can analyze the code within the function without any help. However, when a function has references to or from code outside that function, it becomes almost impossible for Rust to figure out the lifetimes of the parameters or return values on its own. The lifetimes might be different each time the function is called. This is why we need to annotate the lifetimes manually.

https://doc.rust-lang.org/book/ch12-04-testing-the-librarys-functionality.html

Rust can’t possibly know which of the two arguments we need, so we need to tell it.

https://doc.rust-lang.org/book/ch13-01-closures.html:

Type annotations are required on functions because they’re part of an explicit interface exposed to your users. Defining this interface rigidly is important for ensuring that everyone agrees on what types of values a function uses and returns.

Description of the problem:

Chapters 3 and 10 fail to get at the crux of why types and lifetimes are required in function signatures. Ironically, the best explanation comes in chapter 13, while contrasting closures with functions.

Suggested fix:

  • In chapter 3, shift the emphasis to how types-in-function-signatures form a contract: they're part of the docs generated for your function; you won't accidentally break callers by changing the internals of your function; compiler error messages never have to show you internals of other functions. Being able to (mostly) predict where the compiler will require type annotations is nice, too!
  • In chapter 10, explain that lifetime annotations are required in function signatures because they are part of the contract, just as types are part of the contract (linking back to chapter 3). Remove the quoted bits about the borrow checker being "unable" to figure out the actual lifetime relationships; this seems to be contradicted by compiler error messages that often tell you exactly what lifetime relationships to add.
  • In chapter 13, consider having the quoted sentence link back to the section in chapter 3.

Related issues:

  • #2296

Related quote from Steve Klabnik on Discord:

"it is a deliberate design choice, it's not about things being possible or impossible. rust kinda considers the signature the contact; if it inferred signatures, changes to the body could change your API. this leads to easier broken APIs, as well as spooky action-at-a-distance type errors. "write types in signatures and basically nowhere else" is a sweet spot, basically

jruderman avatar Sep 09 '21 16:09 jruderman