Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.8k views
in Technique[技术] by (71.8m points)

rust - Confused about using trait with lifetime as generic parameter constraint

I am trying to make some kind of decoder, that will be able to deserialize entries without actually copying memory, just by mapping values to some memory regions. That is what I currently managed to do (simplified for testcase):

#![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

In fact it compiles and even works, but now I cannot understand how to use my Read trait as generic parameter. For example, suppose I want to compare a value to a decoded result of some memory area:

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));
}

It fails with "borrowed value does not live long enough", and I can guess why: because mem lifetime is the function body, not 'a, as I did specify in signature for input parameter. So I tried to use second lifetime paramater as shown:

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

But it also didn't work for obvious reason. So I really don't understand how to make compare_to_smth work without passing memory chunk from outside. Is there any solution, or I should refactor the code somehow?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Unfortunately, what you want to do is inexpressible in Rust at the moment.

The signature of Read trait that actually would work is as follows (in pseudo-Rust):

trait<'r> Read for Self<'r> {
    fn read<'a>(mem: &'a [u8]) -> Result<Self<'a>, String>;  // '
}

That is, Self must be a higher-kinded type in its lifetime parameter. This requires support for higher-kinded types, which is a very desired feature in the Rust community but which is still yet to be implemented.

The problem with the original signature:

trait Read<'a> {
    fn read(mem: &'a [u8]) -> Result<Self, String>;
}

is that 'a is a parameter to the trait. When this trait is used as a trait bound:

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

it means that the caller of this function chooses the actual lifetime parameter. For example, the caller may choose 'static:

fn compare_to_smth<T>(value: &T) -> bool where T: PartialEq+Read<'static>

However, the function uses &[u8] whose lifetime is not 'static.

In fact, this concrete example may be not exactly correct due to variance (I imagine it would be sound for this lifetime to be 'static here, but variance of lifetimes itself is somewhat confusing, so I'm not really sure), but nevertheless the general idea is the same: in order for this to work the Read::read method must be polymorphic in the lifetime of its argument and result, but you can't write such signature yet.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to WuJiGu Developer Q&A Community for programmer and developer-Open, Learning and Share
...