-
Notifications
You must be signed in to change notification settings - Fork 37
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom ctx override for derive macro #106
Changes from all commits
35c979c
8859cd9
b8ebe4a
4951257
e17150c
e4825b2
f3cf244
bb4aea6
840fad3
618e4cc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
use scroll_derive::{Pread, Pwrite, SizeWith}; | ||
|
||
/// An example of using a method as the value for a ctx in a derive. | ||
struct EndianDependent(Endian); | ||
impl EndianDependent { | ||
fn len(&self) -> usize { | ||
match self.0 { | ||
scroll::Endian::Little => 5, | ||
scroll::Endian::Big => 6, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq)] | ||
struct VariableLengthData { | ||
buf: Vec<u8>, | ||
} | ||
|
||
impl<'a> TryFromCtx<'a, usize> for VariableLengthData { | ||
type Error = scroll::Error; | ||
|
||
fn try_from_ctx(from: &'a [u8], ctx: usize) -> Result<(Self, usize), Self::Error> { | ||
let offset = &mut 0; | ||
let buf = from.gread_with::<&[u8]>(offset, ctx)?.to_owned(); | ||
Ok((Self { buf }, *offset)) | ||
} | ||
} | ||
impl<'a> TryIntoCtx<usize> for &'a VariableLengthData { | ||
type Error = scroll::Error; | ||
fn try_into_ctx(self, dst: &mut [u8], ctx: usize) -> Result<usize, Self::Error> { | ||
let offset = &mut 0; | ||
for i in 0..(ctx.min(self.buf.len())) { | ||
dst.gwrite(self.buf[i], offset)?; | ||
} | ||
Ok(*offset) | ||
} | ||
} | ||
impl SizeWith<usize> for VariableLengthData { | ||
fn size_with(ctx: &usize) -> usize { | ||
*ctx | ||
} | ||
} | ||
|
||
#[derive(Debug, PartialEq, Pread, Pwrite, SizeWith)] | ||
#[repr(C)] | ||
struct Data { | ||
id: u32, | ||
timestamp: f64, | ||
// You can fix the ctx regardless of what is passed in. | ||
#[scroll(ctx = BE)] | ||
arr: [u16; 2], | ||
// You can use arbitrary expressions for the ctx. | ||
// You have access to the `ctx` parameter of the `{pread/gread}_with` inside the expression. | ||
// TODO(implement) you have access to previous fields. | ||
// TODO(check) will this break structs with fields named `ctx`?. | ||
#[scroll(ctx = EndianDependent(ctx.clone()).len())] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. one last thing I'll say: i think the example is actually better if EndianDependent is the ctx for VariableLength data, and inside of it's impl, it calls That is clearer and more idiomatic scroll, imho, if there is such a thing :D However, i understand that you are showing off arbitrary expressions are callable in the ctx here, which is very cool and probably worth retaining as you've written it. Side note: I'm surprised you have to Side Side note: if the ctx does need to be cloned, and you don't clone, how terrible is the error message presented to the user (at derive time)? I imagine it could be pretty bad? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But if you follow the directions:
The fn size_with(ctx: &::scroll::Endian) but the try_{from,into}_ctx derives make this code: fn try_into_ctx(
self,
dst: &mut [u8],
ctx: ::scroll::Endian,
) -> and fn try_from_ctx(
src: &'a [u8],
ctx: ::scroll::Endian,
) -> Yeah, its a little hacky :) If |
||
custom_ctx: VariableLengthData, | ||
} | ||
|
||
use scroll::{ | ||
ctx::{SizeWith, TryFromCtx, TryIntoCtx}, | ||
Endian, Pread, Pwrite, BE, LE, | ||
}; | ||
|
||
fn main() { | ||
let bytes = [ | ||
0xefu8, 0xbe, 0xad, 0xde, 0, 0, 0, 0, 0, 0, 224, 63, 0xad, 0xde, 0xef, 0xbe, 0xaa, 0xbb, | ||
0xcc, 0xdd, 0xee, | ||
]; | ||
let data: Data = bytes.pread_with(0, LE).unwrap(); | ||
println!("data: {data:?}"); | ||
assert_eq!(data.id, 0xdeadbeefu32); | ||
assert_eq!(data.arr, [0xadde, 0xefbe]); | ||
let mut bytes2 = vec![0; ::std::mem::size_of::<Data>()]; | ||
bytes2.pwrite_with(data, 0, LE).unwrap(); | ||
let data: Data = bytes.pread_with(0, LE).unwrap(); | ||
let data2: Data = bytes2.pread_with(0, LE).unwrap(); | ||
assert_eq!(data, data2); | ||
// Not enough bytes because of ctx dependent length being too long. | ||
assert!(bytes.pread_with::<Data>(0, BE).is_err()) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is so wild hah, i have to think about usecases/what it means, or if there's some kind of hygiene issue that a different design might not have, but it's very interesting! is the fact that
ctx
is available within the scope of the pread something you have made available or is it a property of derive with field overrides in macros?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because the user's expression is effectively copy-pasted without hygiene and so the parameters of the trait method are accessible. It would take more work to remove this ability, since we'd have to parse for an instance of a
ctx
ident and actively block it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also means that if only
Pwrite
derive is used (for example), they won't get an error usingself
ordst
idents.