casey / casey/x-serialization-format
Core traits
- Dominant language
- Rust
- Stars
- 1
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
I'm starting to converge on a design I like. The whole thing revolves around three traits, but I'm not entirely sure what to name these traits. I'll describe them with the names that I've been using so far:
### `Storage`
`Storage` is a marker trait, meaning it has no methods or associated types. To implement `Storage` for a type `T`, `T` must:
- Have alignment == 1
- Have no padding
- All bit patterns which are `size_of::()` must be valid values
This ensures that it is safe to cast a `&[u8]` of the correct size to a `&T`.
### `Data`
Anything that implements `Data` must have alignment 1 and no padding, but some bit patterns may be invalid, or some bit patterns may be conditionally invalid depending on the rest of the buffer they're in. Implementors must provide a function `check`, as well as an associated type that implements `Storage`:
```rust
unsafe trait Data<'a>: Sized {
type Storage: Storage + 'a;
fn check(storage: &Self::Storage, buffer: &[u8]) -> Result<()>;
}
```
`Data::check` receives a reference to the associated type `Self::Storage`, as well as the entire buffer containing that `Self::Storage` instance. If check succeeds, then it should be safe to cast that `Self::Storage` reference to a `Self` reference.
### `Value`
The `Value` trait looks like this:
```rust
trait Value<'a> {
type Data: Data<'a>;
fn from_data(data: &'a Self::Data) -> Self;
}
```
## Yes but why?
Types implementing `Storage` are the base types that can always be loaded from a raw buffer, without extra checks. data will provide a number of such types, including little-endian integers, probably named `U32`, `U64`, etc.
Types implementing `Data` require some additional validity checking, like checking that a bool is 0 or 1, or that a u32 is a valid codepoint, or that an offset points to another valid value within the buffer. A blanket impl will implement `Data` for all types that implement `Storage`.
`Value` is for defining a conversion from a `Data` value to a "nice" rust native value. The reason for this is that many common rust values cannot implement `Data` safely. For example, I don't think the layout of `&str` is defined, and even if it were, it would consist of two `usize` fields (pointer and length), which are of different sizes and endianness on different platforms. A blanket impl will implement `Value` for all types that implement `Data`.
So, `Value` is there to provide a mapping from a "nice" value, to the not so nice `Data` value that it should be stored as. This will allow you to declare a struct or table getter like `fn foo(&self) -> &str`, which internally will convert from a field of type `struct DataString { offset: LittleEndianU64, len: LittleEndianU64 } ` to a `&str`.
## Names
I'm a little meh on the names. I think `Storage` is good. `Data` is nice and short, but it's not entirely clear why it should be named after the crate itself. `Value` is okay, but isn't very descriptive.
## Annoyingness
It's somewhat annoying that there have to be three traits, but I think they're necessary :P
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.