pub trait Archive {
type Archived;
type Resolver;
unsafe fn resolve(
&self,
pos: usize,
resolver: Self::Resolver,
out: *mut Self::Archived
);
}
Expand description
A type that can be used without deserializing.
Archive
is one of three basic traits used to work with zero-copy data and controls the layout
of the data in its archived zero-copy representation. The Serialize
trait helps transform
types into that representation, and the Deserialize
trait helps transform types back out.
Types that implement Archive
must have a well-defined archived size. Unsized types can be
supported using the ArchiveUnsized
trait, along with SerializeUnsized
and
DeserializeUnsized
.
Archiving is done depth-first, writing any data owned by a type before writing the data for the type itself. The type must be able to create the archived type from only its own data and its resolver.
Archived data is always treated as if it is tree-shaped, with the root owning its direct
descendents and so on. Data that is not tree-shaped can be supported using special serializer
and deserializer bounds (see ArchivedRc
for example). In a buffer of
serialized data, objects are laid out in reverse order. This means that the root object is
located near the end of the buffer and leaf objects are located near the beginning.
Examples
Most of the time, #[derive(Archive)]
will create an acceptable implementation. You can use the
#[archive(...)]
and #[archive_attr(...)]
attributes to control how the implementation is
generated. See the Archive
derive macro for more details.
use rkyv::{Archive, Deserialize, Serialize};
#[derive(Archive, Deserialize, Serialize, Debug, PartialEq)]
// This will generate a PartialEq impl between our unarchived and archived types
#[archive(compare(PartialEq))]
// We can pass attributes through to generated types with archive_attr
#[archive_attr(derive(Debug))]
struct Test {
int: u8,
string: String,
option: Option<Vec<i32>>,
}
let value = Test {
int: 42,
string: "hello world".to_string(),
option: Some(vec![1, 2, 3, 4]),
};
// Serializing is as easy as a single function call
let bytes = rkyv::to_bytes::<_, 256>(&value).unwrap();
// Or you can customize your serialization for better performance
// and compatibility with #![no_std] environments
use rkyv::ser::{Serializer, serializers::AllocSerializer};
let mut serializer = AllocSerializer::<0>::default();
serializer.serialize_value(&value).unwrap();
let bytes = serializer.into_serializer().into_inner();
// You can use the safe API with the validation feature turned on,
// or you can use the unsafe API (shown here) for maximum performance
let archived = unsafe { rkyv::archived_root::<Test>(&bytes[..]) };
assert_eq!(archived, &value);
// And you can always deserialize back to the original type
let deserialized: Test = archived.deserialize(&mut rkyv::Infallible).unwrap();
assert_eq!(deserialized, value);
Note: the safe API requires the validation
feature.
Many of the core and standard library types already have Archive
implementations available,
but you may need to implement Archive
for your own types in some cases the derive macro cannot
handle.
In this example, we add our own wrapper that serializes a &'static str
as if it’s owned.
Normally you can lean on the archived version of String
to do most of the work, or use the
Inline
to do exactly this. This example does everything to demonstrate
how to implement Archive
for your own types.
use core::{slice, str};
use rkyv::{
archived_root,
ser::{Serializer, serializers::AlignedSerializer},
out_field,
AlignedVec,
Archive,
Archived,
ArchiveUnsized,
MetadataResolver,
RelPtr,
Serialize,
SerializeUnsized,
};
struct OwnedStr {
inner: &'static str,
}
struct ArchivedOwnedStr {
// This will be a relative pointer to our string
ptr: RelPtr<str>,
}
impl ArchivedOwnedStr {
// This will help us get the bytes of our type as a str again.
fn as_str(&self) -> &str {
unsafe {
// The as_ptr() function of RelPtr will get a pointer the str
&*self.ptr.as_ptr()
}
}
}
struct OwnedStrResolver {
// This will be the position that the bytes of our string are stored at.
// We'll use this to resolve the relative pointer of our
// ArchivedOwnedStr.
pos: usize,
// The archived metadata for our str may also need a resolver.
metadata_resolver: MetadataResolver<str>,
}
// The Archive implementation defines the archived version of our type and
// determines how to turn the resolver into the archived form. The Serialize
// implementations determine how to make a resolver from the original value.
impl Archive for OwnedStr {
type Archived = ArchivedOwnedStr;
// This is the resolver we can create our Archived verison from.
type Resolver = OwnedStrResolver;
// The resolve function consumes the resolver and produces the archived
// value at the given position.
unsafe fn resolve(
&self,
pos: usize,
resolver: Self::Resolver,
out: *mut Self::Archived,
) {
// We have to be careful to add the offset of the ptr field,
// otherwise we'll be using the position of the ArchivedOwnedStr
// instead of the position of the relative pointer.
let (fp, fo) = out_field!(out.ptr);
self.inner.resolve_unsized(
pos + fp,
resolver.pos,
resolver.metadata_resolver,
fo,
);
}
}
// We restrict our serializer types with Serializer because we need its
// capabilities to archive our type. For other types, we might need more or
// less restrictive bounds on the type of S.
impl<S: Serializer + ?Sized> Serialize<S> for OwnedStr {
fn serialize(
&self,
serializer: &mut S
) -> Result<Self::Resolver, S::Error> {
// This is where we want to write the bytes of our string and return
// a resolver that knows where those bytes were written.
// We also need to serialize the metadata for our str.
Ok(OwnedStrResolver {
pos: self.inner.serialize_unsized(serializer)?,
metadata_resolver: self.inner.serialize_metadata(serializer)?
})
}
}
let mut serializer = AlignedSerializer::new(AlignedVec::new());
const STR_VAL: &'static str = "I'm in an OwnedStr!";
let value = OwnedStr { inner: STR_VAL };
// It works!
serializer.serialize_value(&value).expect("failed to archive test");
let buf = serializer.into_inner();
let archived = unsafe { archived_root::<OwnedStr>(buf.as_ref()) };
// Let's make sure our data got written correctly
assert_eq!(archived.as_str(), STR_VAL);
Associated Types
The archived representation of this type.
In this form, the data can be used with zero-copy deserialization.
Required methods
Creates the archived version of this value at the given position and writes it to the given output.
The output should be initialized field-by-field rather than by writing a whole struct. Performing a typed copy will mark all of the padding bytes as uninitialized, but they must remain set to the value they currently have. This prevents leaking uninitialized memory to the final archive.
Safety
pos
must be the position ofout
within the archiveresolver
must be the result of serializing this object