References and Lifetimes

Refs, Ownership, Lifetimes

  • Can take a reference to an owned thing

      let y = 5;
      let ry = &y;
    
  • Now you have an obligation: ry must not outlive y

    • C/C++ will happily let you write things like

      int y = 5; int *ry = &y; return ry;

    even though the returned pointer is now pointing into whereever on the stack the now-lost y was

  • Rust compiler tracks this for you, so that e.g.

      let y = 5;
      let ry = &y;
      return ry;
    

    will give a compile-time error

Mutable Refs

  • Refs come in two varieties: "mutable" and "immutable" (really "exclusive" and "shared")

  • Can only take mutable ref to mutable thing

      let mut y = 5;
      let ry = &mut y;
    
  • Once you take a mutable ref, you've essentially "borrowed" the value referred to

    • Must not go out of scope
    • Cannot take any more references while it is live
    • Owner can't do anything with the value while it is live (read it, change it, move it, drop it)

Immutable Refs

  • An immutable ref is essentially "shared". You can take lots of them if you want

      let y = 5;
      let ry1 = &y;
      let ry2 = &y;
    
  • You are still restricted for safety

    • Owner cannot drop or move value (nor mutate, duh) while refs are live

More About Refs

  • A lot of automatic derefing happens

    • With the "." structure / enum operator
    • With comparison operators
    • etc

      let y = 10; let ry = &y; let rry = &ry; assert!(9 < rry);

  • There's no such thing as a "null reference" (in safe code): no way to produce one, no need to guard against them

    • If you need a "nullable" value, use the Option type

      let y = 10; let ory = Some(&y);

    • This is true for refs or anything else

  • You can get a reference to an anonymous variable implicitly defined by an expression

      let ry = &10;
    

Explicit Lifetimes

  • The machinery that the compiler uses to check lifetimes is by default "under the hood": does the checking for you without intervention

  • Sometimes, though, you need (or want) to get explicit access to that machinery to allow a program to compile that is safe but won't by default

  • "Named lifetimes" start with a tick, e.g. 'a ("tick-a")

  • In many contexts, explicit lifetime names can be declared

      fn f<'a, 'b>(x: &u64, y: &u64) -> &u64
    
  • These names can then be used to describe lifetime constraints for referenced data

      fn f<'a, 'b>(x: &'a u64, y: &'b u64) -> &'a u64
    

    In this example, the lifetime of the returned data must the same as the lifetime of x's data

      fn f<'a, 'b: 'a>(x: &'a u64, y: &'b u64) -> &'a u64
    

    We can also require that result's data live at least as long as y's

      fn f<'a, 'b: 'a>(x: &'a u64, y: &'b u64) -> &'b u64
    

    https://play.rust-lang.org/?gist=59636f6153699652df21d05ea61f3428&version=stable

Book Example

Rust Parametric Types

  • Actually "monomorphic" or "template" types, but…

  • Just like we can provide lifetime variables, can provide type variables to get "generic" thingies

      fn id<T>(x: T) -> T {
          x
      }
    

    Can now call with whatever type as long as they match

      assert_eq!(id(5u32), 5u32);
      assert_eq!(id("hello"), "hello");
    
Last modified: Thursday, 12 April 2018, 6:24 PM