rustparametersreferencescoping

I can't understand the Rust "scope" definition (Rust Programming Language, 2nd Ed. Klabnik & Nichols)


I'm having a hard time with how Rust defines "scope". Or perhaps it's with the definition of "scope" in the No Starch Press book (I actually bought the book).

On the top of pg. 62:

A scope is the range within a program for which an item is valid.

In the next paragraph:

The variable is valid from the point at which it's declared until the end of the current scope.

To my mind, this definition of "scope" is recursive.

Now flip over the the "References and Borrowing section pg. 72:

The scope in which the variable (parameter) "s" is valid is the same as any function parameter's scope.

So the scope of the param is the scope of the param. That doesn't help me.

And now to 2nd paragraph on pg. 75:

... a reference's scope starts from where it is introduced and continues through the last time that reference is used.

Ah-ha. So a variable can go out of scope before the end of it's "context" (function).

Q1: Is the definition of "scope" different for a variable defined within a function vs. a parameter passed to that function?

Q2: Is the scoping handling for references different than for other variables?

Q3: When a function param or a variable declared within a function goes out of scope before the end of the function, is the variable's storage released at that time? I.e. prior to the function's return to caller?


Solution

  • There are actually three different scopes. A block's scope, a variable's scope, and a reference's referent's scope.

    A block (curly braces, { ... }) creates a scope. This scope means the variable declared (directly) inside the block will live until its end. A function also creates a scope, in the same way.

    A variable's scope is basically its liveness - where it is alive. It is usually from its declaration until the end of the block (or the function) it is declared in. However, a variable may be moved or dropped and then its life will end before or after the block. Whether its scope ends there or at the block's end (and the value's liveness is longer/shorter) is up to a debate.

    So when the book says:

    A scope is the range within a program for which an item is valid.

    "scope" here refers to the variable's (item's) scope.

    And when it says:

    The variable is valid from the point at which it's declared until the end of the current scope.

    "scope" here refers to the block's scope.

    The third scope is a reference's scope, which the book refers to in pg. 75. Usually, a reference's scope is the same as the scope of the item it refers to, but because of Non-Lexical Lifetimes, it may end earlier, after the reference's last use. This impact when other references to the same item can be used, due to Rust's borrowing rules (shared xor mutable).

    Regarding your questions:

    Is the definition of "scope" different for a variable defined within a function vs. a parameter passed to that function?

    Both are in the function's scope (if the variable is not declared inside an inner block) . Variables defined in the function are dropped before parameters, though, so we can argue their scope ends earlier.

    Is the scoping handling for references different than for other variables?

    Not exactly, but they are considered no longer alive after their last use and not when their block ends. This is not unique to references, however, this is true for any variable that does not have a drop glue.

    When a function param or a variable declared within a function goes out of scope before the end of the function, is the variable's storage released at that time? I.e. prior to the function's return to caller?

    It depends on how you define "memory released".

    The only guarantee Rust makes is that after returning from the function, the stack pointer will be the same as before entering it. Usually, it is only decreased when the function returns, to do it at once instead of many times and save CPU time.

    Another guarantee Rust makes is that Drop is called deterministically. A variable's Drop is called at the end of its block scope, unless it was moved from. So resources owned by the variable (such as heap allocations) are released there.

    Whether the stack memory of the variable is released depends on how you define this. Sometimes the compiler can reuse the stack memory of variables if their lifetimes are not overlapping. Other times it does not, but it will not get used after the variable's scope ends.