特质签名中“自我”的生命周期参数

时间:2015-11-02 16:05:30

标签: rust

考虑这个简单的协议实现:

#[derive(PartialEq, Debug)]
enum Req<'a> {
    InputData(&'a [u8]),
    Stop,
}

impl<'a> Req<'a> {
    fn decode(packet: &'a [u8]) -> Result<Req<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Req::InputData(&packet[1..])),
            Some(&0x02) => Ok(Req::Stop),
            _ => Err(format!("invalid request: {:?}", packet)),
        }
    }
}

#[derive(PartialEq, Debug)]
enum Rep<'a> {
    OutputData(&'a [u8]),
    StopAck,
}

impl<'a> Rep<'a> {
    fn decode(packet: &'a [u8]) -> Result<Rep<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Rep::OutputData(&packet[1..])),
            Some(&0x02) => Ok(Rep::StopAck),
            _ => Err(format!("invalid reply: {:?}", packet)),
        }
    }
}

fn assert_req(packet: Vec<u8>, sample: Req) {
    assert_eq!(Req::decode(&packet), Ok(sample));
}

fn assert_rep(packet: Vec<u8>, sample: Rep) {
    assert_eq!(Rep::decode(&packet), Ok(sample));
}

fn main() {
    assert_req(vec![1, 2, 3], Req::InputData(&[2, 3]));
    assert_req(vec![2], Req::Stop);
    assert_rep(vec![1, 2, 3], Rep::OutputData(&[2, 3]));
    assert_rep(vec![2], Rep::StopAck);
}

playground

这样可行,但两个函数assert_reqassert_rep具有相同的代码,只有类型的差异。编写一个通用assert_packet

是个好主意
trait Decode<'a>: Sized {
    fn decode(packet: &'a [u8]) -> Result<Self, String>;
}

#[derive(PartialEq, Debug)]
enum Req<'a> {
    InputData(&'a [u8]),
    Stop,
}

impl<'a> Decode<'a> for Req<'a> {
    fn decode(packet: &'a [u8]) -> Result<Req<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Req::InputData(&packet[1..])),
            Some(&0x02) => Ok(Req::Stop),
            _ => Err(format!("invalid request: {:?}", packet)),
        }
    }
}

#[derive(PartialEq, Debug)]
enum Rep<'a> {
    OutputData(&'a [u8]),
    StopAck,
}

impl<'a> Decode<'a> for Rep<'a> {
    fn decode(packet: &'a [u8]) -> Result<Rep<'a>, String> {
        match packet.first() {
            Some(&0x01) => Ok(Rep::OutputData(&packet[1..])),
            Some(&0x02) => Ok(Rep::StopAck),
            _ => Err(format!("invalid reply: {:?}", packet)),
        }
    }
}

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
where
    T: Decode<'a> + PartialEq + std::fmt::Debug,
{
    assert_eq!(T::decode(&packet), Ok(sample));
}

fn main() {
    assert_packet(vec![1, 2, 3], Req::InputData(&[2, 3]));
    assert_packet(vec![2], Req::Stop);
    assert_packet(vec![1, 2, 3], Rep::OutputData(&[2, 3]));
    assert_packet(vec![2], Rep::StopAck);
}

playground

然而,这会触发“活不够久”的错误:

error[E0597]: `packet` does not live long enough
  --> src/main.rs:41:27
   |
41 |     assert_eq!(T::decode(&packet), Ok(sample));
   |                           ^^^^^^ does not live long enough
42 | }
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 37:1...
  --> src/main.rs:37:1
   |
37 | / fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
38 | | where
39 | |     T: Decode<'a> + PartialEq + std::fmt::Debug,
40 | | {
41 | |     assert_eq!(T::decode(&packet), Ok(sample));
42 | | }
   | |_^

如果我理解正确,问题出在函数签名中:

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
where
    T: Decode<'a>

此处,函数返回时会销毁packet,但用户提供的'a生命周期参数表示生命周期应该在assert_packet函数之外的某处结束。有没有正确的解决方案?签名应该怎么样?也许higher rank trait bounds可以帮到这里吗?

2 个答案:

答案 0 :(得分:3)

为什么要编译:

fn assert_req(packet: Vec<u8>, sample: Req) {
    assert_eq!(Req::decode(&packet), Ok(sample));
}

虽然这不是?

fn assert_packet<'a, T>(packet: Vec<u8>, sample: T) where T: Decode<'a> + PartialEq + std::fmt::Debug {
    assert_eq!(T::decode(&packet), Ok(sample));
}

不同之处在于,在第一个版本中,Req的两个文本事件命名了Req<'a>结构的两个不同实例,具有两个不同的生命周期。第一次出现在sample参数上,专门用于assert_req函数接收的生命周期参数。第二次出现,用于调用decode,专门用于packet参数本身的生命周期(一旦函数返回就会停止存在)。这意味着assert_eq!的两个参数的类型不同;但是,它会进行编译,因为Req<'a>可以强制转换为Req<'b>,其中'b的生命周期比'a更短(Req<'a>Req<'b>的子类型})。

另一方面,在第二个版本中,T的出现次数必须代表完全相同的类型。始终假设生命周期参数表示比函数调用更长的生命周期,因此调用T::decode的生命周期更短是错误的。

这是一个较短的功能,表现出同样的问题:

fn decode_packet<'a, T>(packet: Vec<u8>) where T: Decode<'a> {
    T::decode(&packet);
}

此函数无法编译:

<anon>:38:16: 38:22 error: `packet` does not live long enough
<anon>:38     T::decode(&packet);
                         ^~~~~~

可以在此函数上使用更高级别的特征边界来进行编译:

fn decode_packet<T>(packet: Vec<u8>) where for<'a> T: Decode<'a> {
    T::decode(&packet);
}

但是,现在我们还有另一个问题:我们无法调用此功能!如果我们尝试像这样调用它:

fn main() {
    decode_packet(vec![1, 2, 3]);
}

我们收到此错误:

<anon>:55:5: 55:18 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282]
<anon>:55     decode_packet(vec![1, 2, 3]);
              ^~~~~~~~~~~~~

那是因为我们没有指定我们想要使用哪个Decode实现,并且没有编译器可用于推断此信息的参数。

如果我们指定实施怎么办?

fn main() {
    decode_packet::<Req>(vec![1, 2, 3]);
}

我们收到此错误:

<anon>:55:5: 55:25 error: the trait `for<'a> Decode<'a>` is not implemented for the type `Req<'_>` [E0277]
<anon>:55     decode_packet::<Req>(vec![1, 2, 3]);
              ^~~~~~~~~~~~~~~~~~~~

这不起作用,因为我们写的Req实际上被解释为Req<'_>,其中'_是编译器推断的生命周期。对于所有可能的生命周期,这不会实现Decode,仅适用于一个特定的生命周期。

为了能够实现您想要的功能,Rust必须支持higher kinded types。然后可以在函数上定义类型构造函数参数(而不是类型参数)。例如,您可以将ReqRep作为需要生命周期参数的类型构造函数传递,以生成特定的Req<'a>类型。

答案 1 :(得分:0)

如果您将所涉及的生命周期与您引用的输入相关联,它就能正常工作,例如

fn assert_packet<'a, T>(packet: &'a [u8], sample: T) where T: Decode<'a> ..

(您还需要更新测试以通过借用而非拥有Vec。)