简体   繁体   中英

type parameter for function vs struct (lifetime issue)

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?

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

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
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM