-
Notifications
You must be signed in to change notification settings - Fork 0
The Rust Half
An app's Rust half is a fn pointer in the registry, and not a child process.
Home and File Explorer show what the orchestrator already knows. A pipe in front of that data buys an IPC boundary to talk to ourselves. A tool needs that boundary, because a tool is another team's code on its own release cycle. An app is this code.
type Dispatch = fn(&AppHandle, &CallContext, &str, Option<Value>) -> Result<Value, RpcError>;pub fn call(
app: &AppHandle,
context: &CallContext,
method: &str,
params: Option<Value>,
) -> Result<Value, RpcError> {
match method {
"notes/list" => encode(&list(app, context)?),
"notes/write" => {
let (path, body) = fields(params.as_ref())?;
write(app, context, &path, &body)?;
encode(&list(app, context)?)
}
_ => Err(RpcError::new(
METHOD_NOT_FOUND,
format!("no such method: {method}"),
)),
}
}Three habits show in that block:
-
Return the new state from a mutation.
notes/writeanswers with the list. The frontend redraws from one reply and never guesses what changed. -
Name the method in the error.
no such method: {method}tells the reader which call arrived. -
Keep the parsing in a helper.
fieldsreadsparamsand returnsINVALID_PARAMSwith a sentence, and not a code path name.
helve/hello never reaches your code. The shell answers the handshake in ToolWindow.tsx, because the shell is the side that knows the session.
pub struct CallContext {
pub cluster_id: Option<String>,
pub project: Option<PathBuf>,
}The context reports where the call came from. cluster_id is the cluster holding the calling surface. project is that cluster's project, already checked against the disk.
Both fields hold None in ordinary states, and not in failure states. A call arrives with no instance id when the shell's own menu raises it. A cluster holds no project until a developer points it at one. Home draws a pick-a-project screen for that state, and File Explorer falls back to the stack root.
When a method has nothing sensible to do without a cluster, ask for one:
let cluster = context.require_cluster()?;The shell resolves instance_id from event.source against its map of mounted iframes. A frame cannot name itself, which makes the context trustworthy.
files::call holds none. Every method takes its root from the CallContext the caller resolved, which reports where the frame sits, and not which app is in it. An Explorer and a Viewer in one cluster then resolve the same project, while a pair in the next cluster resolve theirs.
Two apps share one dispatch where the data is one thing. viewer and files both point at files::call, because the machine has one filesystem. A second module means a second files/read, a second files/write, and a third chance for the pair to disagree.
State that outlives a restart goes to a file beside settings.json in the OS config directory. apps/tutorial.rs is the short example.
use helve_rpc::{RpcError, INTERNAL_ERROR, INVALID_PARAMS, METHOD_NOT_FOUND};| Code | Use it for |
|---|---|
METHOD_NOT_FOUND |
A method this app does not serve |
INVALID_PARAMS |
Params that are missing, or that name a record the app cannot find |
INTERNAL_ERROR |
A failure inside the app: encoding, the filesystem, the OS |
Write the message for the person reading the screen. Name the state, and give the reader a next step:
RpcError::new(
INVALID_PARAMS,
format!("no such tutorial: {id}"),
)STANDARDS.md §5 covers Rust errors, modules and commands. §1 covers the import layering. src-tauri/src/apps/mod.rs carries the registry, with the reasoning in comments beside the rows.