Files
start-os/core/core-rust-patterns.md
2026-02-14 08:15:50 -07:00

5.5 KiB

Utilities & Patterns

This document covers common utilities and patterns used throughout the StartOS codebase.

Util Module (core/src/util/)

The util module contains reusable utilities. Key submodules:

Module Purpose
actor/ Actor pattern implementation for concurrent state management
collections/ Custom collection types
crypto.rs Cryptographic utilities (encryption, hashing)
future.rs Future/async utilities
io.rs File I/O helpers (create_file, canonicalize, etc.)
iter.rs Iterator extensions
net.rs Network utilities
rpc.rs RPC helpers
rpc_client.rs RPC client utilities
serde.rs Serialization helpers (Base64, display/fromstr, etc.)
sync.rs Synchronization primitives (SyncMutex, etc.)

Command Invocation (Invoke trait)

The Invoke trait provides a clean way to run external commands with error handling:

use crate::util::Invoke;

// Simple invocation
tokio::process::Command::new("ls")
    .arg("-la")
    .invoke(ErrorKind::Filesystem)
    .await?;

// With timeout
tokio::process::Command::new("slow-command")
    .timeout(Some(Duration::from_secs(30)))
    .invoke(ErrorKind::Timeout)
    .await?;

// With input
let mut input = Cursor::new(b"input data");
tokio::process::Command::new("cat")
    .input(Some(&mut input))
    .invoke(ErrorKind::Filesystem)
    .await?;

// Piped commands
tokio::process::Command::new("cat")
    .arg("file.txt")
    .pipe(&mut tokio::process::Command::new("grep").arg("pattern"))
    .invoke(ErrorKind::Filesystem)
    .await?;

Guard Pattern

Guards ensure cleanup happens when they go out of scope.

GeneralGuard / GeneralBoxedGuard

For arbitrary cleanup actions:

use crate::util::GeneralGuard;

let guard = GeneralGuard::new(|| {
    println!("Cleanup runs on drop");
});

// Do work...

// Explicit drop with action
guard.drop();

// Or skip the action
// guard.drop_without_action();

FileLock

File-based locking with automatic unlock:

use crate::util::FileLock;

let lock = FileLock::new("/path/to/lockfile", true).await?;  // blocking=true
// Lock held until dropped or explicitly unlocked
lock.unlock().await?;

Mount Guard Pattern (core/src/disk/mount/guard.rs)

RAII guards for filesystem mounts. Ensures filesystems are unmounted when guards are dropped.

MountGuard

Basic mount guard:

use crate::disk::mount::guard::MountGuard;
use crate::disk::mount::filesystem::{MountType, ReadOnly};

let guard = MountGuard::mount(&filesystem, "/mnt/target", ReadOnly).await?;

// Use the mounted filesystem at guard.path()
do_something(guard.path()).await?;

// Explicit unmount (or auto-unmounts on drop)
guard.unmount(false).await?;  // false = don't delete mountpoint

TmpMountGuard

Reference-counted temporary mount (mounts to /media/startos/tmp/):

use crate::disk::mount::guard::TmpMountGuard;
use crate::disk::mount::filesystem::ReadOnly;

// Multiple clones share the same mount
let guard1 = TmpMountGuard::mount(&filesystem, ReadOnly).await?;
let guard2 = guard1.clone();

// Mount stays alive while any guard exists
// Auto-unmounts when last guard is dropped

GenericMountGuard trait

All mount guards implement this trait:

pub trait GenericMountGuard: std::fmt::Debug + Send + Sync + 'static {
    fn path(&self) -> &Path;
    fn unmount(self) -> impl Future<Output = Result<(), Error>> + Send;
}

SubPath

Wraps a mount guard to point to a subdirectory:

use crate::disk::mount::guard::SubPath;

let mount = TmpMountGuard::mount(&filesystem, ReadOnly).await?;
let subdir = SubPath::new(mount, "data/subdir");

// subdir.path() returns the full path including subdirectory

FileSystem Implementations (core/src/disk/mount/filesystem/)

Various filesystem types that can be mounted:

Type Description
bind.rs Bind mounts
block_dev.rs Block device mounts
cifs.rs CIFS/SMB network shares
ecryptfs.rs Encrypted filesystem
efivarfs.rs EFI variables
httpdirfs.rs HTTP directory as filesystem
idmapped.rs ID-mapped mounts
label.rs Mount by label
loop_dev.rs Loop device mounts
overlayfs.rs Overlay filesystem

Other Useful Utilities

Apply / ApplyRef traits

Fluent method chaining:

use crate::util::Apply;

let result = some_value
    .apply(|v| transform(v))
    .apply(|v| another_transform(v));

Container<T>

Async-safe optional container:

use crate::util::Container;

let container = Container::new(None);
container.set(value).await;
let taken = container.take().await;

HashWriter<H, W>

Write data while computing hash:

use crate::util::HashWriter;
use sha2::Sha256;

let writer = HashWriter::new(Sha256::new(), file);
// Write data...
let (hasher, file) = writer.finish();
let hash = hasher.finalize();

Never type

Uninhabited type for impossible cases:

use crate::util::Never;

fn impossible() -> Never {
    // This function can never return
}

let never: Never = impossible();
never.absurd::<String>()  // Can convert to any type

MaybeOwned<'a, T>

Either borrowed or owned data:

use crate::util::MaybeOwned;

fn accept_either(data: MaybeOwned<'_, String>) {
    // Use &*data to access the value
}

accept_either(MaybeOwned::from(&existing_string));
accept_either(MaybeOwned::from(owned_string));

new_guid()

Generate a random GUID:

use crate::util::new_guid;

let guid = new_guid();  // Returns InternedString