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);
}
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?
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):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:
is that
'a
is a parameter to the trait. When this trait is used as a trait bound:it means that the caller of this function chooses the actual lifetime parameter. For example, the caller may choose
'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 theRead::read
method must be polymorphic in the lifetime of its argument and result, but you can't write such signature yet.I think the issue might be more in the signature of
compare_to_smth
.but then inside the function you're doing:
I may be wrong, but I don't think this depends on how
Read
is defined and how sophisticated the type system can become in the future, as whatever you write on the right side doesn't change the fact that you're expecting a T on the left side (and T has to outlive'a
).A "sufficiently smart compiler" might be able to see that smth_value is actually not living longer than
'a
and that what you're doing is safe, but in general this would be unsafe. I'm sure that using an unsafe transmute incompare_to_smth
completely defeats your purpose, but just to demonstrate, this works: