``` ____ ()==( (@==() '___'| | | | ἀρετή | )_____| ()==( (@==() '--------------'
```
https://docs.rs/scroll
Add to your Cargo.toml
toml
[dependencies]
scroll = "0.7.0"
Scroll implements several traits for read/writing generic containers (byte buffers are currently implemented by default). Most familiar will likely be the Pread
trait, which at its basic takes an immutable reference to self, an immutable offset to read at, (and a parsing context, more on that later), and then returns the deserialized value.
Because self is immutable, all reads can be performed in parallel and hence are trivially parallelizable.
A simple example demonstrates its flexibility:
```rust use scroll::{ctx, Pread, LE}; let bytes: [u8; 4] = [0xde, 0xad, 0xbe, 0xef];
// reads a u32 out of b
with the endianness of the host machine, at offset 0, turbofish-style
let number: u32 = bytes.pread::
//If the type is known another way by the compiler, say reading into a struct field, we can omit the turbofish, and type ascription altogether!
// If we want, we can explicitly add a endianness to read with by calling pread_with
.
// The following reads a u32 out of b
with Big Endian byte order, at offset 0
let benumber: u32 = bytes.preadwith(0, scroll::BE).unwrap();
// or a u16 - specify the type either on the variable or with the beloved turbofish
let benumber2 = bytes.preadwith::
// Scroll has core friendly errors (no allocation). This will have the type scroll::Error::BadOffset
because it tried to read beyond the bound
let byte: scroll::Result
// Scroll is extensible: as long as the type implements TryWithCtx
, then you can read your type out of the byte array!
// We can parse out custom datatypes, or types with lifetimes
// if they implement the conversion trait TryFromCtx
; here we parse a C-style \0 delimited &str (safely)
let hello: &[u8] = b"helloworld\0more words";
let helloworld: &str = hello.pread(0).unwrap();
asserteq!("helloworld", hello_world);
// ... and this parses the string if its space separated! use scroll::ctx::*; let spaces: &[u8] = b"hello world some junk"; let world: &str = spaces.preadwith(6, StrCtx::Delimiter(SPACE)).unwrap(); asserteq!("world", world); ```
std::io
APIScroll can also read/write simple types from a std::io::Read
or std::io::Write
implementor. The built-in numeric types are taken care of for you. If you want to read a custom type, you need to implement the FromCtx
(how to parse) and SizeWith
(how big the parsed thing will be) traits. You must compile with default features. For example:
```rust use std::io::Cursor; use scroll::IOread; let bytes_ = [0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0xef,0xbe,0x00,0x00,]; let mut bytes = Cursor::new(bytes_);
// this will bump the cursor's Seek
let foo = bytes.ioread::
Similarly, we can write to anything that implements std::io::Write
quite naturally:
```rust use scroll::{IOwrite, LE, BE}; use std::io::{Write, Cursor};
let mut bytes = [0x0u8; 10]; let mut cursor = Cursor::new(&mut bytes[..]); cursor.writeall(b"hello").unwrap(); cursor.iowritewith(0xdeadbeef as u32, BE).unwrap(); asserteq!(cursor.intoinner(), [0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xde, 0xad, 0xbe, 0xef, 0x0]); ```
Scroll is designed to be highly configurable - it allows you to implement various context (Ctx
) sensitive traits, which then grants the implementor automatic uses of the Pread
and/or Pwrite
traits.
For example, suppose we have a datatype and we want to specify how to parse or serialize this datatype out of some arbitrary byte buffer. In order to do this, we need to provide a TryFromCtx impl for our datatype.
In particular, if we do this for the [u8]
target, using the convention (usize, YourCtx)
, you will automatically get access to
calling pread_with::<YourDatatype>
on arrays of bytes.
```rust use scroll::{self, ctx, Pread, BE, Endian};
struct Data<'a> { name: &'a str, id: u32, }
// note the lifetime specified here
impl<'a> ctx::TryFromCtx<'a, Endian> for Data<'a> {
type Error = scroll::Error;
type Size = usize;
// and the lifetime annotation on &'a [u8]
here
fn tryfromctx (src: &'a [u8], endian: Endian)
-> Result<(Self, Self::Size), Self::Error> {
let offset = &mut 0;
let name = src.gread::<&str>(offset)?;
let id = src.gread_with(offset, endian)?;
Ok((Data { name: name, id: id }, *offset))
}
}
let bytes = b"UserName\x00\x01\x02\x03\x04"; let data = bytes.preadwith::(0, BE).unwrap(); asserteq!(data.id, 0x01020304); asserteq!(data.name.tostring(), "UserName".to_string()); ```
Please see the official documentation, or a simple example for more.
Any ideas, thoughts, or contributions are welcome!