3

Consider the following test case:

#![allow(unstable)]
trait Choose<'o> { 
    fn choose(a: &'o u64, b: &'o u32) -> Self; 
}

impl<'o> Choose<'o> for &'o u64 { 
    fn choose(a: &'o u64, _b: &'o u32) -> &'o u64 { a }
}

impl<'o> Choose<'o> for &'o u32 { 
    fn choose(_a: &'o u64, b: &'o u32) -> &'o u32 { b }
} // '

struct Handler {
    a: u64,
    b: u32,
}

impl Handler {
    fn new() -> Handler {
        Handler { a: 14, b: 15 }
    }

    fn find<'a, V, W>(&'a mut self, value: W) -> Option<V> where V: Choose<'a>, W: PartialEq<V> { // '
        let v = Choose::choose(&self.a, &self.b);
        if value == v {
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    let mut h = Handler::new();

    {
        let v_a = h.find::<&u64, &u64>(&14u64);
        println!("v_a = {:?}", v_a);
    }

    {
        let v_b = h.find::<&u64, &u64>(&15u64);
        println!("v_b = {:?}", v_b);
    }
}

playpen

Suppose I have some changing state inside Handler::find, so I need &mut self. But both v_a and v_b variables pointing to Handler internals live inside their own blocks, so there is no borrow problems here. In this case a type parameter V is specified for a find method directly, and everything compiles as expected.

But then I move parameter V into Handler type signature and it stops compiling with "cannot borrow h as mutable more than once at a time" error:

#![allow(unstable)]
trait Choose<'o> { 
    fn choose(a: &'o u64, b: &'o u32) -> Self; 
}

impl<'o> Choose<'o> for &'o u64 { 
    fn choose(a: &'o u64, _b: &'o u32) -> &'o u64 { a }
}

impl<'o> Choose<'o> for &'o u32 { 
    fn choose(_a: &'o u64, b: &'o u32) -> &'o u32 { b }
} // '

struct Handler<V> {
    a: u64,
    b: u32,
}

impl<V> Handler<V> {
    fn new() -> Handler<V> {
        Handler { a: 14, b: 15 }
    }

    fn find<'a, W>(&'a mut self, value: W) -> Option<V> where V: Choose<'a>, W: PartialEq<V> { // '
        let v = Choose::choose(&self.a, &self.b);
        if value == v {
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    let mut h = Handler::<&u64>::new();

    {
        let v_a = h.find(&14u64);
        println!("v_a = {:?}", v_a);
    }

    {
        let v_b = h.find(&15u64);
        println!("v_b = {:?}", v_b);
    }
}

playpen

I really cannot understand the difference. Why mutable borrow is not released after variable v_a is dead?

swizard
  • 2,551
  • 1
  • 18
  • 26
  • "But then I move parameter V into Handler type signature" why do you do that, when `Handler` doesn't have any members of that type? – Shepmaster Jan 24 '15 at 23:19
  • Because with parameter in method signature I'm able to use both find::<&u64> and find::<&u32> for one Handler instance, and I want to force compiler forbid it. – swizard Jan 24 '15 at 23:27

2 Answers2

1

I think what is happening here is: in your main, when you do let mut h = Handler::<&u64>::new();, your Handler is now tied to the lifetime of that reference to u64. So even if v_a dies in the following block, the lifetime of V must be that of h, which is still alive.

The problem, by the way, is not so much in the code you already wrote, but in the code you or somebody else could still write. Given your definition of Handler with an unconstrained V, I could go ahead and do:

// in the meanwhile, in another crate...
// I create another trait
trait MyTrait {
    fn foo(&self) -> &u64;
}

// and implement it for Handler<&u64>
impl<'a> MyTrait for Handler<&'a u64> {
    fn foo(&self) -> &u64 { &self.a }
}

and then this would be legal:

let h = Handler::<&u64>::new();    
println!("{}", h.foo()); // prints 14

So, whenever I do let h = Handler::<&u64>::new(); like you did, the only safe option is for the &64 to live at least as long as h.

If you could use u64 as V, instead of &u64 you would be fine. Something like this changes your program very little (note that I'm still working with references, not passing by value), but allows you to parametrize Handler for u32/64 instead of &u32/64 :

trait Choose<'o> { 
    fn choose(a: &'o u64, b: &'o u32) -> &'o Self; 
}

impl<'o> Choose<'o> for u64 { 
    fn choose(a: &'o u64, _b: &'o u32) -> &'o u64 { a }
}

impl<'o> Choose<'o> for u32 { 
    fn choose(_a: &'o u64, b: &'o u32) -> &'o u32 { b }
}

struct Handler<V> {
    a: u64,
    b: u32,
}

impl<V> Handler<V> {
    fn new() -> Handler<V> {
        Handler { a: 14, b: 15 }
    }

    fn find<'a, W>(&'a mut self, value: W) -> Option<&'a V> where V: Choose<'a>, W: PartialEq<&'a V> {
        let v = Choose::choose(&self.a, &self.b);
        if value == v {
            Some(v)
        } else {
            None
        }
    }
}

fn main() {
    let mut h = Handler::<u64>::new();

    {
        let v_a = h.find(&14u64);
        println!("v_a = {:?}", v_a);
    }

    {
        let v_b = h.find(&15u64);
        println!("v_b = {:?}", v_b);
    }
}

playpen

Paolo Falabella
  • 24,914
  • 3
  • 72
  • 86
  • This seems like a similar solution as [described here](http://stackoverflow.com/a/27950119/155423), and I swear there was another question from the OP asking the same thing in a slightly different manner but I can't find it. – Shepmaster Jan 25 '15 at 17:06
0

Here's my understanding of the problem, others may be able to provide more concrete explanations.

By adding the type parameter to your struct, you are able to store that type in the struct. Since you also specify that your type has the trait Choose<'a> and 'a is tied to the lifetime of self, Rust has to assume that you are potentially going to store a (mutable) reference to the struct when you make the function call. The compiler must then transfer your mutable borrow to the function, and it doesn't know when it ends. The only safe time is when the object itself goes out of scope

Here's an example of storing a V:

fn find<'a, W>(&'a mut self, value: W) -> Option<V> where V: Choose<'a>, W: PartialEq<V> { //'
    let v = Choose::choose(&self.a, &self.b);

    self.c = Some(Choose::choose(&self.a, &self.b)); // saved

    if value == v {
        Some(v)
    } else {
        None
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • But there is a trait function signature: "fn choose(a: &'o u64, _b: &'o u32) -> &'o u64" which says, that lifetime of the result is the same as lifetime of the parameters, which is the same lifetime as &self? Even with storing a reference to the struct inside object, what bad things can happen? – swizard Jan 24 '15 at 23:54
  • @Shepmaster, it's impossible to do what you have described because you can't store references into the struct inside that struct. – Vladimir Matveev Jan 25 '15 at 06:32
  • @VladimirMatveev I must be misunderstanding you. The code I pasted compiles and runs (in the context of the OP code). Theres also [this answer](http://stackoverflow.com/a/28114482/155423) that describes some tricks to keep references to your own struct (for better or worse). – Shepmaster Jan 25 '15 at 17:02