函数与结构的类型参数(生命周期问题)
type parameter for function vs struct (lifetime issue)
考虑以下测试用例:
#![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。但是指向 v_a 和 v_b 变量都指向 Handler 内部他们自己的块,所以这里没有借用问题。在这种情况下,类型参数 V 直接指定给 find 方法,一切都按预期编译。
但随后我将参数 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);
}
}
我真的无法理解其中的区别。为什么可变借用在变量v_a死后没有释放?
这是我对问题的理解,其他人或许可以提供更具体的解释。
通过将类型参数添加到您的结构中,您可以将该类型存储在结构中。由于您还指定您的类型具有 Choose<'a>
和 'a
与 self
的生命周期相关的特征,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
}
}
我认为这里发生的事情是:
在您的 main
中,当您执行 let mut h = Handler::<&u64>::new();
时,您的处理程序现在与对 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 参数化 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);
}
}
考虑以下测试用例:
#![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。但是指向 v_a 和 v_b 变量都指向 Handler 内部他们自己的块,所以这里没有借用问题。在这种情况下,类型参数 V 直接指定给 find 方法,一切都按预期编译。
但随后我将参数 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);
}
}
我真的无法理解其中的区别。为什么可变借用在变量v_a死后没有释放?
这是我对问题的理解,其他人或许可以提供更具体的解释。
通过将类型参数添加到您的结构中,您可以将该类型存储在结构中。由于您还指定您的类型具有 Choose<'a>
和 'a
与 self
的生命周期相关的特征,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
}
}
我认为这里发生的事情是:
在您的 main
中,当您执行 let mut h = Handler::<&u64>::new();
时,您的处理程序现在与对 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 参数化 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);
}
}