【问题标题】:Confused about using trait with lifetime as generic parameter constraint对使用具有生命周期的特征作为通用参数约束感到困惑
【发布时间】:2015-01-19 16:44:28
【问题描述】:

我正在尝试制作某种解码器,它能够反序列化条目而无需实际复制内存,只需将值映射到某些内存区域即可。这就是我目前设法做的(简化为测试用例):

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

playpen

实际上它可以编译甚至可以工作,但现在我无法理解如何使用我的 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));
}

它以“借来的值不够长”而失败,我可以猜到原因:因为 mem 生命周期是函数体,而不是 'a,因为我确实在签名中指定了输入参数。所以我尝试使用第二个生命周期参数,如图所示:

fn compare_to_smth<'a, 'b, T>(value: &'a T) -> bool where T: PartialEq+Read<'b> {

但它也没有明显的原因。所以我真的不明白如何在不从外部传递内存块的情况下使 compare_to_smth 工作。有什么解决办法,或者我应该以某种方式重构代码?

【问题讨论】:

  • 根据您之前的问题,您还应该声明您不能将 'aRead 特征移动到 read 方法,这将是我的第一个建议。
  • 但在这种情况下我似乎无法移动它,因为我需要为 U8AndStr 实现 trait。

标签: rust


【解决方案1】:

不幸的是,目前你想做的事情在 Rust 中是无法表达的。

实际可行的Read trait 的签名如下(在伪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 是 trait 的参数。当此 trait 用作 trait bound 时:

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>

但是,该函数使用&amp;[u8],其生命周期不是'static

事实上,由于方差,这个具体的例子可能并不完全正确(我想这里的生命周期是'static 是合理的,但是生命周期的方差本身有点令人困惑,所以我不太确定),但总体思路是相同的:为了使其工作,Read::read 方法在其参数和结果的生命周期内必须是多态的,但您还不能编写这样的签名。

【讨论】:

  • 感谢您的回复!也许您可以帮助我找到使用当前可用的抽象在 rust 中实现我的想法的正确方法?
  • 不幸的是,我认为目前不可能。当我尝试提高一些代码的类型安全性时,我也遇到过这种情况(尽管我需要参数化关联类型,而不是 Self)。我找不到解决办法。
【解决方案2】:

我认为问题可能更多在于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 寿命更长)。

“足够聪明的编译器”可能会看到 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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-25
    • 2017-03-22
    • 2014-05-05
    • 2020-06-26
    相关资源
    最近更新 更多