Hacker News new | ask | show | jobs
by liara_k 2446 days ago
Variables go "out of scope" (in at least one sense) at last use, but are not `Drop`-ed (de-allocated, etc...) until the end of the function. The difference is important because of rust's rule against simultaneous aliasing and mutability. Consider this example:

  fn main() {
    let mut a = 1;
    let b = &mut a;
    *b = 2;
    println!("{}", a); // prints "2"
    // *b = 4; // If this line is uncommented, compile time error.
  }
Because b is a mutable reference to a, this means that a cannot be accessed directly until b goes out of scope. In this sense, b goes out of scope the last time it's used. _However_, AFAIK, b isn't actually de-allocated until the end of the function.

Of course, it doesn't matter in this trivial case, because b is just some bytes in the current stack frame so there's nothing to actually de-allocate. But if b were a complex type that _also_ had some memory to de-allocate, this wouldn't happen until the end of main(). But in this case, b's scope also lasts until the end of main, which is kind of like adding that last line back in...

This can be seen in the following example, where b has an explicit type:

  struct B<'a>(&'a mut i32);
  impl<'a> Drop for B<'a> {
    fn drop(&mut self) {
      // We'd still have a mutable reference to a here...
      // If B owned resources and needed to free them, this is where that would happen
    }
  }
  fn main() {
    let mut a = 1;
    let b = B(&mut a);
    *b.0 = 2;
    std::mem::drop(b); // Comment this line out, get compiler error
    println!("{}", a); // prints "2"
  }

In this example, without the std::mem::drop() line, the implementation for Drop (i.e., B's destructor), B::drop would be implicitly called at the end of the function. But in that case, B::drop() would still have a mutable reference to a, which makes the println call produce a "cannot borrow `a` as immutable because it is also borrowed as mutable" compile time error.

In other words, this "going out of scope at last use" is really about rust's lifetimes system, not memory allocation.

IMHO... this is one of the rough edges in rust's somewhat steep learning curve. Rust's lifetimes rules make the language kind of complicated, though getting memory safety in a systems programming language is worth the trade-off. There's a lot of syntactic sugar that makes things a LOT easier and less verbose in most cases, but the learning curve trade-off for _that_ is that, when you _do_ run into the more complex cases that the compiler can't figure out for you, it's easy to get lost, because there are a few extra puzzle pieces to fit together. Still way better than the foot-gun that is C, though. At least for me... YMMV, obviously.