摆弄生命周期:对不安全的生命周期重新解释进行健全性检查
Fiddling with lifetimes: sanity check on unsafe reinterpretation of lifetimes
假设您有两个 T
实例的集合(此处为简单起见 Vec
),以及一个函数来计算这些集合中的元素是否出现在其中一个或两个中:
// With lifetimes not yet annotated
fn comm(left: &Vec<T>, right: &Vec<T>) -> Vec<(Tag, &T)> {}
enum Tag {
Left,
Both,
Right,
}
comm(l,r)
保证 在 T
出现在 left
只有 和 T
都存在。
但是,由于某些 T
可能仅出现在 right
中,因此函数的完整签名必须如下所示:
fn comm<'a, 'b, 'c>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec(Tag, &'c T)
where
'a: 'c,
'b: 'c,
实际问题,那么:我知道,在 (tag, &T)
元组之一中返回 comm
,如果 tag == Left or tag == Both
,则 &T
将 肯定指向left
集合。
使用 mem::transmute
或其他机制获取 comm
返回的引用之一并将其转换为与 left
集合相匹配的生命周期是否明智、安全和合法?
例如:
fn last_common<'a, 'b>(left: &'a Vec<T>, right: &'b Vec<T>) -> &'a T {
let tagged = comm(left, right);
let (tag, ref_to_T) = boring code that picks one tuple from tagged...
assert!(matches!(tag, Tag::Left) || matches!(tag, Tag::Both))
return std::mem::transmute::<&'_ T, &'a T>(ref_to_T);
}
是的,声音很好。事实上,transmute()
的官方文档说它可以用来延长生命周期:
https://doc.rust-lang.org/stable/std/mem/fn.transmute.html#examples
Extending a lifetime, or shortening an invariant lifetime. This is advanced, very unsafe Rust!
struct R<'a>(&'a i32);
unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> {
std::mem::transmute::<R<'b>, R<'static>>(r)
}
unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>)
-> &'b mut R<'c> {
std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r)
}
但我不会推荐它。相反,我建议您使用枚举:
fn comm<'a, 'b, T>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec<Tag<'a, 'b, T>> {}
enum Tag<'a, 'b, T> {
Left(&'a T),
Both(&'a T), // Could be `&'b T`, too.
Right(&'b T),
}
你也可以有一个方法来提取生命周期较短的值,比如:
impl<'a, T> Tag<'a, 'a, T> {
pub fn value(self) -> &'a T {
let (Self::Left(v) | Self::Right(v) | Self::Both(v)) = self;
v
}
}
假设您有两个 T
实例的集合(此处为简单起见 Vec
),以及一个函数来计算这些集合中的元素是否出现在其中一个或两个中:
// With lifetimes not yet annotated
fn comm(left: &Vec<T>, right: &Vec<T>) -> Vec<(Tag, &T)> {}
enum Tag {
Left,
Both,
Right,
}
comm(l,r)
保证 在 T
出现在 left
只有 和 T
都存在。
但是,由于某些 T
可能仅出现在 right
中,因此函数的完整签名必须如下所示:
fn comm<'a, 'b, 'c>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec(Tag, &'c T)
where
'a: 'c,
'b: 'c,
实际问题,那么:我知道,在 (tag, &T)
元组之一中返回 comm
,如果 tag == Left or tag == Both
,则 &T
将 肯定指向left
集合。
使用 mem::transmute
或其他机制获取 comm
返回的引用之一并将其转换为与 left
集合相匹配的生命周期是否明智、安全和合法?
例如:
fn last_common<'a, 'b>(left: &'a Vec<T>, right: &'b Vec<T>) -> &'a T {
let tagged = comm(left, right);
let (tag, ref_to_T) = boring code that picks one tuple from tagged...
assert!(matches!(tag, Tag::Left) || matches!(tag, Tag::Both))
return std::mem::transmute::<&'_ T, &'a T>(ref_to_T);
}
是的,声音很好。事实上,transmute()
的官方文档说它可以用来延长生命周期:
https://doc.rust-lang.org/stable/std/mem/fn.transmute.html#examples
Extending a lifetime, or shortening an invariant lifetime. This is advanced, very unsafe Rust!
struct R<'a>(&'a i32); unsafe fn extend_lifetime<'b>(r: R<'b>) -> R<'static> { std::mem::transmute::<R<'b>, R<'static>>(r) } unsafe fn shorten_invariant_lifetime<'b, 'c>(r: &'b mut R<'static>) -> &'b mut R<'c> { std::mem::transmute::<&'b mut R<'static>, &'b mut R<'c>>(r) }
但我不会推荐它。相反,我建议您使用枚举:
fn comm<'a, 'b, T>(left: &'a Vec<T>, right: &'b Vec<T>) -> Vec<Tag<'a, 'b, T>> {}
enum Tag<'a, 'b, T> {
Left(&'a T),
Both(&'a T), // Could be `&'b T`, too.
Right(&'b T),
}
你也可以有一个方法来提取生命周期较短的值,比如:
impl<'a, T> Tag<'a, 'a, T> {
pub fn value(self) -> &'a T {
let (Self::Left(v) | Self::Right(v) | Self::Both(v)) = self;
v
}
}