1

I have a test in progress that does not compile:

#[test]
fn node_cost_dequeue() {
    let mut queue: BinaryHeap<&NodeCost> = BinaryHeap::new();
    let cost_1: NodeCost = NodeCost::new(1, 50, 0);
    let cost_2: NodeCost = NodeCost::new(2, 30, 0);
    let cost_3: NodeCost = NodeCost::new(3, 80, 0);
    queue.push(&cost_1);
    queue.push(&cost_2);
    queue.push(&cost_3);
    assert_eq!(2, (*queue.pop().unwrap()).id);
}

Results in error: cost_1 does not live long enough

With the additional info that "borrowed value dropped before borrower".

So I attempt to add explicit lifetime annotations.

#[test]
fn node_cost_dequeue() {
    let mut queue: BinaryHeap<&'a NodeCost> = BinaryHeap::new();
    let cost_1: NodeCost<'a> = NodeCost::new(1, 50, 0);
    let cost_2: NodeCost<'a> = NodeCost::new(2, 30, 0);
    let cost_3: NodeCost<'a> = NodeCost::new(3, 80, 0);
    queue.push(&cost_1);
    queue.push(&cost_2);
    queue.push(&cost_3);
    assert_eq!(2, (*queue.pop().unwrap()).id);
}

This results in use of undeclared lifetime name 'a.

So, I attempt to declare it on the function

fn node_cost_dequeue<'a>() -> () {

But this results in error: functions used as tests must have signature fn() -> ()

Am I on the right track? How do I declare this lifetime?

Synesso
  • 37,610
  • 35
  • 136
  • 207
  • You can find a somewhat similar case (though with a hidden leak due to the same lifetime of both affected objects) in [this question](http://stackoverflow.com/q/39827244/1870153). – ljedrz Oct 07 '16 at 02:55
  • See also [How to initialize a variable with a lifetime?](http://stackoverflow.com/questions/28108689/how-to-initialize-a-variable-with-a-lifetime) => you cannot, a lifetime *annotation* does not enforce a lifetime, it merely documents it. – Matthieu M. Oct 07 '16 at 06:45

1 Answers1

6

What this is telling you is..

Everything is dropped in reverse order.. cost_1 will be dropped before queue.. because its declared after queue. So queue has a reference to cost_1 and because the values are now being dropped at the end of the method queue now has a reference to... nothing? cost_1 has been dropped while queue is still "alive" - even though its about to be dropped as well.

To be clear, the sequence of events matters because the BinaryHeap instance is storing references. It does not own its contents.

The sequence of events will be:

  • Declare a BinaryHeap instance that stores references.
  • Declare cost_1, cost_2 and cost_3, whose references will be inserted into the BinaryHeap.
  • Add references into BinaryHeap.
  • Perform assertion.
  • Drop cost_3.
  • Drop cost_2.
  • Drop cost_1.
  • What do the BinaryHeap references point to now? The items are dropped.
  • Drop BinaryHeap.

The bolded item in the above list is what the compiler is protecting you from. Potentially invalid pointers.

The easy fix.. is to reverse the declaration order such that any references to cost_1 outlive queue:

let cost_1: NodeCost = NodeCost::new(1, 50, 0);
let cost_2: NodeCost = NodeCost::new(2, 30, 0);
let cost_3: NodeCost = NodeCost::new(3, 80, 0);
let mut queue: BinaryHeap<&NodeCost> = BinaryHeap::new();

Here is a working example on the playground

The order of events will now be:

  • Declare cost_1, cost_2 and cost_3, whose references will be inserted into the BinaryHeap.
  • Declare a BinaryHeap instance that stores references.
  • Add references into BinaryHeap.
  • Perform assertion.
  • Drop BinaryHeap. The references can go away because the items they reference are still alive.
  • Drop cost_3.
  • Drop cost_2.
  • Drop cost_1.
Simon Whitehead
  • 63,300
  • 9
  • 114
  • 138