问题
Consider this simple protocol implementation:
#[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
This works, but the two functions assert_req
and assert_rep
have identical code with only a difference in types. It is a good idea to write one generic 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
However, this triggers a "does not live long enough" error:
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 | | }
| |_^
If I understand correctly, the problem is in the function signature:
fn assert_packet<'a, T>(packet: Vec<u8>, sample: T)
where
T: Decode<'a>
Here, packet
is destroyed when the function returns, but the user-provided 'a
lifetime parameter says that the lifetime should end somewhere outside the assert_packet
function. Is there any right solution? How should the signature look? Maybe higher rank trait bounds could help here?
回答1:
Why does this compile:
fn assert_req(packet: Vec<u8>, sample: Req) {
assert_eq!(Req::decode(&packet), Ok(sample));
}
while this doesn't?
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));
}
The difference is that in the first version, the two textual occurrences of Req
name two different instances of the Req<'a>
struct, with two different lifetimes. The first occurrence, on the sample
parameter, is specialized with a lifetime parameter received by the assert_req
function. The second occurrence, used to invoke decode
, is specialized with the lifetime of the packet
parameter itself (which ceases to exist as soon as the function returns). This means that the two arguments to assert_eq!
don't have the same type; yet, it compiles because Req<'a>
can be coerced to a Req<'b>
where 'b
is a shorter lifetime than 'a
(Req<'a>
is a subtype of Req<'b>
).
On the other hand, in the second version, both occurrences of T
must represent the exact same type. Lifetime parameters are always assumed to represent lifetimes that are longer than the function call, so it's an error to invoke T::decode
with a lifetime that is shorter.
Here's a shorter function that exhibits the same problem:
fn decode_packet<'a, T>(packet: Vec<u8>) where T: Decode<'a> {
T::decode(&packet);
}
This function fails to compile:
<anon>:38:16: 38:22 error: `packet` does not live long enough
<anon>:38 T::decode(&packet);
^~~~~~
It's possible to use higher rank trait bounds on this function to make it compile:
fn decode_packet<T>(packet: Vec<u8>) where for<'a> T: Decode<'a> {
T::decode(&packet);
}
However, now we have another problem: we can't invoke this function! If we try to invoke it like this:
fn main() {
decode_packet(vec![1, 2, 3]);
}
We get this error:
<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]);
^~~~~~~~~~~~~
That's because we didn't specify which implementation of Decode
we want to use, and there are no parameters the compiler can use to infer this information.
What if we specify an implementation?
fn main() {
decode_packet::<Req>(vec![1, 2, 3]);
}
We get this error:
<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]);
^~~~~~~~~~~~~~~~~~~~
That doesn't work, because the Req
we wrote is actually interpreted as Req<'_>
, where '_
is a lifetime inferred by the compiler. This doesn't implement Decode
for all possible lifetimes, only for one particular lifetime.
In order to be able to implement your desired function, Rust would have to support higher kinded types. It would then be possible to define a type constructor parameter (instead of a type parameter) on the function. For instance, you'd be able to pass Req
or Rep
as a type constructor requiring a lifetime parameter to produce a specific Req<'a>
type.
回答2:
It works correctly if you link the lifetimes involved to the input which you'll be referencing, e.g.
fn assert_packet<'a, T>(packet: &'a [u8], sample: T) where T: Decode<'a> ..
(You'll also need to update the tests to pass borrows rather than owned Vec
s.)
来源:https://stackoverflow.com/questions/33481868/lifetime-parameter-for-self-in-trait-signature