函数vs结构的类型参数(生命周期问题) [英] type parameter for function vs struct (lifetime issue)

查看:67
本文介绍了函数vs结构的类型参数(生命周期问题)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

考虑以下测试案例:

#![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);
    }
}

游戏围栏

假设我在 Handler :: find 中有一些变化的状态,所以我需要& mut self .但是指向 Handler 内部的 v_a v_b 变量都位于它们自己的块中,因此这里没有借用问题.在这种情况下,直接为 find 方法指定类型参数 V ,并且所有内容都会按预期编译.

但是随后我将参数 V 移到了 Handler 类型签名中,并且它停止编译,并出现无法多次借用h作为可变变量"错误:

#![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);
    }
}

游戏围栏

我真的不明白其中的区别.为什么变量 v_a 死后不释放可变借位?

解决方案

我认为这里发生的是: 在main中,当您执行let mut h = Handler::<&u64>::new();时,您的Handler现在已绑定到对该u64的引用的生存期. 因此,即使v_a在以下代码块中死亡,V的生存期也必须是h的生存期.

顺便说一句,问题不仅仅在于您已经编写的代码,而是您或其他人仍然可以编写的代码. 给定您对Handler的定义,使其具有不受约束的V,我可以继续进行以下操作:

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

然后这将是合法的:

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

所以,每当我像您一样做let h = Handler::<&u64>::new();时,唯一安全的选择是让& 64的寿命至少与h一样长.

如果可以将u64用作V,而不是&u64,则可以.这样的事情几乎不会改变您的程序(请注意,我仍在使用引用,而不是按值传递),但是允许您为u32/64而不是& u32/64设置Handler的参数:

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

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

这篇关于函数vs结构的类型参数(生命周期问题)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆