pub trait BlockRead: Send + Sync {
// Required methods
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>;
fn size_bytes(&self) -> u64;
}Expand description
Read-only random-access block device.
Required Methods§
Sourcefn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>
fn read_at(&self, offset: u64, buf: &mut [u8]) -> Result<()>
Read exactly buf.len() bytes starting at offset (bytes from the
start of the device).
Sourcefn size_bytes(&self) -> u64
fn size_bytes(&self) -> u64
Total device size in bytes. Used for bounds checks.
§It must not change for the life of the device — except through
Callers are entitled to read it once, act on it, and read it again
later expecting the same answer — crate::CachingDevice does
exactly that, bounding a read against it and then clamping each
block it fetches against it. A device whose size moves between the
two makes the cache hold a block shorter than the read it was
fetched for.
An implementation whose length changes UNDERNEATH IT — a second
handle appending to the same file, a volume grown by something
else — must still not follow it. Reopen instead. That is
rust-fs-core#70, and tests/size_stability_contract.rs measures
it: a FileDevice whose backing file grows through another handle
goes on reporting the length it was opened with.
§THE ONE DOOR, AND WHY IT IS NOT A HOLE IN THE CONTRACT
BlockDevice::set_len moves this number, and it is the only
thing that may. The distinction is not “the size is stable except
when it is not”: it is that a caller ASKED, so the moment the
number moves is a moment the caller chose, on a call it made, and
every wrapper on the path is given the chance to keep its own view
consistent as it goes past. size_bytes never surprises a reader;
set_len is the reader.
That is exactly what a device following its backing file could not offer. Nobody is told, nothing is invalidated, and the two halves of one device end up disagreeing about where it ends — which is the defect, not the movement.
A device that answers false to BlockDevice::can_grow cannot
be moved at all, and the stronger, older reading of this contract
holds for it unchanged. Most devices in this crate are in that
class.
§What the implementations here actually do
This used to say “every implementation in this crate takes its size
once, at construction”. That was not true of any of the three
shapes below, and it is the sentence an implementer in a sibling
crate reads before deciding their own device may report a live
length. crate::caching_device’s resizing path and
tests/caching_size_change.rs both used to describe this
differently again, so the crate said three incompatible things
about one contract.
crate::FileDeviceand the slice devices incrate::slicedo take their size once, at construction. The slices keep it forever; aFileDeviceopened read-write on a regular file also answersBlockDevice::can_growwithtrue, so its number moves when — and only when — somebody callsBlockDevice::set_lenon it.crate::ReadOnlyDevice,crate::CountingDeviceandcrate::CachingDeviceFORWARD the question to the device they wrap, on every call, and so are exactly as stable as it is. They cannot be more: a wrapper has no way to hold a moving device still. TheArc<T>,Box<T>and&Tblanket impls below forward the same way.crate::CallbackDevicekeeps its size in a PUBLIC field, so nothing stops a caller moving it after construction. Stability there is the caller’s to keep, not the type’s to enforce.
So the contract binds the implementer; it is not something this
crate’s types guarantee on their behalf. tests/ size_stability_contract.rs tests each of these behaviours rather
than leaving this paragraph to be believed.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".