Development
Project structure
heim
is split into multiple sub-crates, which are gathered together
in the heim
facade crate.
Most of the crates has very obvious names, ex. heim-cpu
, heim-memory
or heim-process
— they are responsible for each separate heim
component.
heim-common
contains commonly used types, such as heim::Result
, heim::Error
,
prelude module for heim
development and also has a bunch
of platform-specific FFI bindings, which are utilized by other heim-*
crates.
heim-runtime
is an abtraction for all supported async runtimes.
Note that instead of the calling, for example, tokio::fs::read_link()
function,
you need to use heim_runtime::fs::read_link()
instead.
Same goes for pin!
/pin_mut!
, join!
and try_join!
macros, all of them are exported by heim-runtime
.
Crate structure
Each crate responsible for the corresponding heim
module has the same files structure.
For example, let's write heim-cpu
from scratch and provide information about CPU statistics.
$ ls /src
os/
sys/
lib.rs
stats.rs
Where:
os/
contains OS-specific traits and implementations, same as Rust doessys/
contains platform-specific implementationslib.rs
should expose all public types and functionsstats.rs
contains theCpuStats
struct and async function which returnsheim::Result<crate::CpuStats>
Public interface
#![allow(unused_variables)] fn main() { use heim_common::prelude::wrap; use crate::sys; /// System CPU stats. pub struct CpuStats(sys::CpuStats); wrap!(CpuStats, sys::CpuStats); }
wrap!
macro generates AsRef
, AsMut
and From
implementations,
which are allowing working with the "inner" sys::CpuStats
struct.
It is strictly important that struct should only has these methods, which are available on all platforms supported, as done in the following example.
#![allow(unused_variables)] fn main() { impl CpuStats { pub fn ctx_switches(&self) -> u64 { self.as_ref().ctx_switches() } pub fn interrupts(&self) -> u64 { self.as_ref().interrupts() } } }
Linux additionally provides the amount of "soft interrupts",
but we can't expose it here, because it would not be portable.
Instead, we should create the CpuStatsExt
trait at os/linux/stats.rs
:
#![allow(unused_variables)] fn main() { pub trait CpuStatsExt { fn soft_interrupts(&self) -> u64; } #[cfg(target_os = "linux")] impl CpuStatsExt for crate::CpuStats { fn soft_interrupts(&self) -> u64 { self.as_ref().soft_interrupts() } } }
Trait itself should be publicly accessible, but impl
block
for our crate::CpuStats
should be gated and implemented only
for target_os = "linux"
Platform implementations
Now we need to create platform-specific implementation
and we will start with the sys/linux/mod.rs
module.
sys/linux/mod.rs
should be compile-gated too with #[cfg(target_os = "linux")]
,
because it can only be used for Linux systems.
Same thing applies to all other platform-specific implementations.
Implementation for our src/stats.rs
goes into the sys/linux/stats.rs
module.
In the case of Linux it will contain few fields, which will be populated later:
#![allow(unused_variables)] fn main() { pub struct CpuStats { ctx_switches: u64, interrupts: u64, soft_interrupts: u64, } impl FromStr for CpuStats { type Err = Error; fn from_str(s: &str) -> Result<CpuStats, Self::Err> { // .. } } }
Now, we need to provide the async interface.
In case of Linux we need to parse the /proc/stat
file
and create the sys::CpuStats
struct with data from it.
Our sys/linux/stats.rs
should declare one function:
#![allow(unused_variables)] fn main() { use heim_common::Result; use heim_runtime as rt; pub async fn cpu_times() -> Result<CpuStats> { rt::fs::read_into("/proc/stat") } }
What will happen here: /proc/stat
will be read asynchronously
with the help of heim_runtime::fs
and then parsed with help of the FromStr
implementation.
Now let's go back to our public CpuStats
struct.
It should declare a similar function too, but its implementation will be much simpler:
#![allow(unused_variables)] fn main() { use heim_common::Result; use crate::sys; pub async fn stats() -> Result<CpuStats> { sys::stats().map(Into::into).await } }
Since that wrap!
macro from the start generates From<sys::CpuStats> for CpuStats
implementation,
all we need now is to call the platform-specific function and wrap the result into public struct.
Same thing applies to all structs and functions returning Future
s and Stream
s --
platform-specific implementations should be received from the Future
or Stream
and wrapped
into a public struct via Into::into
.
Additional
Please, stick to the Rust API guidelines where possible.