对使用具有生命周期的特征作为通用参数约束感到困惑
Confused about using trait with lifetime as generic parameter constraint
我正在尝试制作某种解码器,它能够在不实际复制内存的情况下反序列化条目,只需将值映射到某些内存区域即可。这就是我目前设法做到的(针对测试用例进行了简化):
#![allow(unstable)]
trait CastAbility: Sized { }
impl CastAbility for u64 { }
impl CastAbility for u32 { }
impl CastAbility for u16 { }
impl CastAbility for u8 { }
trait Cast {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a Self, String>;
}
impl<T> Cast for T where T: CastAbility {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a T, String> {
if mem.len() != std::mem::size_of::<T>() {
Err("invalid size".to_string())
} else {
Ok(unsafe { std::mem::transmute(mem.as_ptr()) })
}
}
}
impl Cast for str {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a str, String> {
Ok(unsafe { std::mem::transmute(std::raw::Slice { data: mem.as_ptr(), len: mem.len() }) })
}
}
trait Read<'a> {
fn read(mem: &'a [u8]) -> Result<Self, String>;
}
#[derive(Show, PartialEq)]
struct U8AndStr<'a> {
value_u8: &'a u8,
value_str: &'a str,
}
impl<'a> Read<'a> for U8AndStr<'a> {
fn read(mem: &'a [u8]) -> Result<U8AndStr, String> {
Ok(U8AndStr {
value_u8: try!(Cast::cast(mem.slice(0, 1))),
value_str: try!(Cast::cast(mem.slice(1, mem.len()))),
})
}
}
fn main() {
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let value: U8AndStr = Read::read(mem).unwrap();
println!("value: {:?}", value);
}
事实上它编译甚至可以工作,但现在我不明白如何使用我的 Read 特征作为通用参数。例如,假设我想将一个值与某个内存区域的解码结果进行比较:
fn compare_to_smth<'a, T>(value: &'a T) -> bool where T: PartialEq+Read<'a> {
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let smth_value: T = Read::read(mem).unwrap();
smth_value == *value
}
fn main() {
let value = U8AndStr { value_u8: &1, value_str: "01234567" };
assert!(compare_to_smth(&value));
}
失败并显示 "borrowed value does not live long enough",我可以猜到原因:因为 mem 生命周期是函数体,而不是 'a,正如我在输入参数的签名中指定的那样。所以我尝试使用第二个生命周期参数,如图所示:
fn compare_to_smth<'a, 'b, T>(value: &'a T) -> bool where T: PartialEq+Read<'b> {
但由于显而易见的原因,它也没有奏效。所以我真的不明白如何在不从外部传递内存块的情况下使 compare_to_smth 工作。有没有解决办法,或者我应该以某种方式重构代码?
不幸的是,你想做的事情目前在 Rust 中是无法表达的。
实际有效的 Read
特性的签名如下(在伪 Rust 中):
trait<'r> Read for Self<'r> {
fn read<'a>(mem: &'a [u8]) -> Result<Self<'a>, String>; // '
}
也就是说,Self
在其生命周期参数中必须是更高种类的类型。这需要支持更高种类的类型,这是 Rust 社区非常需要的功能,但仍未实现。
原签名的问题:
trait Read<'a> {
fn read(mem: &'a [u8]) -> Result<Self, String>;
}
是 'a
是特征的参数。当此特征用作特征边界时:
fn compare_to_smth<'a, T>(value: &T) -> bool where T: PartialEq+Read<'a>
表示这个函数的调用者选择了实际的生命周期参数。例如,调用者可以选择 'static
:
fn compare_to_smth<T>(value: &T) -> bool where T: PartialEq+Read<'static>
然而,该函数使用&[u8]
,其生命周期不是'static
。
事实上,由于方差的原因,这个具体的例子可能并不完全正确(我想这辈子在这里 'static
是合理的,但是生命周期的方差本身有点令人困惑,所以我'我不太确定),但总体思路是相同的:为了使其工作,Read::read
方法必须在其参数和结果的生命周期内是多态的,但你还不能编写这样的签名。
我认为问题可能出在 compare_to_smth
的签名上。
fn compare_to_smth<'a, T>(value: &'a T) // this implies a T: 'a bound
// because otherwise we would not be able to
// have a &'a T (references can't live longer
// than the thing they reference)
但是在你正在执行的函数中:
let smth_value: T = Read::read(mem).unwrap();
// give me something of type T that
// lives less than T
我可能错了,但我不认为这取决于 Read
的定义方式以及类型系统将来会变得多么复杂,因为无论您在右侧写什么都不会改变您期望左侧出现 T 的事实(并且 T 必须比 'a
长寿)。
A "sufficiently smart compiler" 可能会看到 smth_value 实际上没有比 'a
长寿,并且您所做的是安全的,但总的来说这是不安全的.
我确信在 compare_to_smth
中使用不安全的转化完全违背了你的目的,但只是为了证明,这是可行的:
fn compare_to_smth<'a, T>(value: &'a T) -> bool
where T: Read<'a> + PartialEq
{
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let smth_value = <U8AndStr as Read>::read(mem).unwrap();
let vl: &U8AndStr = unsafe{ std::mem::transmute(value) };
smth_value == *vl
}
我正在尝试制作某种解码器,它能够在不实际复制内存的情况下反序列化条目,只需将值映射到某些内存区域即可。这就是我目前设法做到的(针对测试用例进行了简化):
#![allow(unstable)]
trait CastAbility: Sized { }
impl CastAbility for u64 { }
impl CastAbility for u32 { }
impl CastAbility for u16 { }
impl CastAbility for u8 { }
trait Cast {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a Self, String>;
}
impl<T> Cast for T where T: CastAbility {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a T, String> {
if mem.len() != std::mem::size_of::<T>() {
Err("invalid size".to_string())
} else {
Ok(unsafe { std::mem::transmute(mem.as_ptr()) })
}
}
}
impl Cast for str {
fn cast<'a>(mem: &'a [u8]) -> Result<&'a str, String> {
Ok(unsafe { std::mem::transmute(std::raw::Slice { data: mem.as_ptr(), len: mem.len() }) })
}
}
trait Read<'a> {
fn read(mem: &'a [u8]) -> Result<Self, String>;
}
#[derive(Show, PartialEq)]
struct U8AndStr<'a> {
value_u8: &'a u8,
value_str: &'a str,
}
impl<'a> Read<'a> for U8AndStr<'a> {
fn read(mem: &'a [u8]) -> Result<U8AndStr, String> {
Ok(U8AndStr {
value_u8: try!(Cast::cast(mem.slice(0, 1))),
value_str: try!(Cast::cast(mem.slice(1, mem.len()))),
})
}
}
fn main() {
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let value: U8AndStr = Read::read(mem).unwrap();
println!("value: {:?}", value);
}
事实上它编译甚至可以工作,但现在我不明白如何使用我的 Read 特征作为通用参数。例如,假设我想将一个值与某个内存区域的解码结果进行比较:
fn compare_to_smth<'a, T>(value: &'a T) -> bool where T: PartialEq+Read<'a> {
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let smth_value: T = Read::read(mem).unwrap();
smth_value == *value
}
fn main() {
let value = U8AndStr { value_u8: &1, value_str: "01234567" };
assert!(compare_to_smth(&value));
}
失败并显示 "borrowed value does not live long enough",我可以猜到原因:因为 mem 生命周期是函数体,而不是 'a,正如我在输入参数的签名中指定的那样。所以我尝试使用第二个生命周期参数,如图所示:
fn compare_to_smth<'a, 'b, T>(value: &'a T) -> bool where T: PartialEq+Read<'b> {
但由于显而易见的原因,它也没有奏效。所以我真的不明白如何在不从外部传递内存块的情况下使 compare_to_smth 工作。有没有解决办法,或者我应该以某种方式重构代码?
不幸的是,你想做的事情目前在 Rust 中是无法表达的。
实际有效的 Read
特性的签名如下(在伪 Rust 中):
trait<'r> Read for Self<'r> {
fn read<'a>(mem: &'a [u8]) -> Result<Self<'a>, String>; // '
}
也就是说,Self
在其生命周期参数中必须是更高种类的类型。这需要支持更高种类的类型,这是 Rust 社区非常需要的功能,但仍未实现。
原签名的问题:
trait Read<'a> {
fn read(mem: &'a [u8]) -> Result<Self, String>;
}
是 'a
是特征的参数。当此特征用作特征边界时:
fn compare_to_smth<'a, T>(value: &T) -> bool where T: PartialEq+Read<'a>
表示这个函数的调用者选择了实际的生命周期参数。例如,调用者可以选择 'static
:
fn compare_to_smth<T>(value: &T) -> bool where T: PartialEq+Read<'static>
然而,该函数使用&[u8]
,其生命周期不是'static
。
事实上,由于方差的原因,这个具体的例子可能并不完全正确(我想这辈子在这里 'static
是合理的,但是生命周期的方差本身有点令人困惑,所以我'我不太确定),但总体思路是相同的:为了使其工作,Read::read
方法必须在其参数和结果的生命周期内是多态的,但你还不能编写这样的签名。
我认为问题可能出在 compare_to_smth
的签名上。
fn compare_to_smth<'a, T>(value: &'a T) // this implies a T: 'a bound
// because otherwise we would not be able to
// have a &'a T (references can't live longer
// than the thing they reference)
但是在你正在执行的函数中:
let smth_value: T = Read::read(mem).unwrap();
// give me something of type T that
// lives less than T
我可能错了,但我不认为这取决于 Read
的定义方式以及类型系统将来会变得多么复杂,因为无论您在右侧写什么都不会改变您期望左侧出现 T 的事实(并且 T 必须比 'a
长寿)。
A "sufficiently smart compiler" 可能会看到 smth_value 实际上没有比 'a
长寿,并且您所做的是安全的,但总的来说这是不安全的.
我确信在 compare_to_smth
中使用不安全的转化完全违背了你的目的,但只是为了证明,这是可行的:
fn compare_to_smth<'a, T>(value: &'a T) -> bool
where T: Read<'a> + PartialEq
{
let mem: &[u8] = &[0x01, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37];
let smth_value = <U8AndStr as Read>::read(mem).unwrap();
let vl: &U8AndStr = unsafe{ std::mem::transmute(value) };
smth_value == *vl
}