函数和结构体的类型参数(生命周期问题)

3

考虑以下测试用例:

#![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

假设我在Handler::find中有一些改变状态的操作,所以我需要使用&mut self。但是v_a和v_b变量都指向Handler内部的不同块,因此这里没有借用问题。在这种情况下,为find方法直接指定类型参数V,然后一切都如预期那样编译通过。

但是当我将参数V移动到Handler类型签名中时,它就无法编译了,并出现了“cannot borrow h as mutable more than once at a time”错误。

#![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

我真的无法理解其中的区别。为什么在变量v_a死亡后,可变借用没有被释放?


但是我将参数V移动到Handler类型签名中,为什么要这样做,因为Handler没有该类型的任何成员? - Shepmaster
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - swizard
2个回答

1
我认为这里发生的事情是: 在你的main中,当你执行let mut h = Handler::<&u64>::new();时,你的Handler现在与对u64的引用的生命周期绑定。 因此,即使v_a在下面的块中死亡,V的生命周期也必须是h的生命周期,而h仍然活着。
顺便说一句,问题不在你已经编写的代码中,而在你或其他人可能仍然编写的代码中。 鉴于你定义了一个没有限制的V的Handler,我可以继续做:
// 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参数化处理程序:

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


这似乎是一个类似的解决方案,就像这里描述的那样,我发誓这个问题的提问者以稍微不同的方式问了同样的问题,但我找不到它。 - Shepmaster

0

这是我对问题的理解,其他人可能能够提供更具体的解释。

通过将类型参数添加到您的结构中,您可以在结构中存储该类型。由于您还指定了您的类型具有特质Choose<'a>并且'aself的生命周期相关联,因此Rust必须假设您在进行函数调用时可能会存储(可变)引用到结构中。编译器必须将您的可变借用转移到函数中,但它不知道何时结束。唯一安全的时间是对象本身超出范围时

以下是存储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
    }
}

但是有一个特征函数签名:“fn choose(a: &'o u64, _b: &'o u32) -> &'o u64”,它表示结果的生命周期与参数的生命周期相同,这与&self的生命周期相同吗?即使在对象内部存储结构体的引用,会发生什么不好的事情? - swizard
@Shepmaster,你所描述的是不可能实现的,因为你无法将引用存储到该结构体内部的结构体中。 - Vladimir Matveev
@VladimirMatveev 我一定是误解了你。我粘贴的代码可以编译并运行(在 OP 代码的上下文中)。还有这个答案,描述了一些技巧来保留对自己结构体的引用(优缺点都有)。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接