How-To Guides#

Step-by-step recipes for running code you don't trust with Rustbox. Each one starts with the short answer, then a Rust example you can copy, using the official rustbox-sdk crate. Every example uses the live API at https://api.rustbox.sh and version 2 of the result format.

Setup#

Add the SDK, an async runtime and serde_json (for reading the evidence block), then create one client and reuse it. The client takes your API key and talks to the live API by default.

cargo add rustbox-sdk serde_json
cargo add tokio --features macros,rt-multi-thread
use rustbox_sdk::{RunRequest, Rustbox};

#[tokio::main]
async fn main() -> rustbox_sdk::Result<()> {
    let client = Rustbox::new(&std::env::var("RUSTBOX_API_KEY").unwrap_or_default())?;

    let execution = client
        .run(&RunRequest {
            language: "python".into(),
            code: "print('hello')".into(),
            stdin: String::new(),
        })
        .await?;

    println!("{:?} {:?}", execution.result.verdict, execution.output.stdout); // Some(Accepted) Some("hello\n")
    Ok(())
}

Every recipe below assumes a client built as in this Setup section, and a small helper to build requests:

fn request(language: &str, code: &str, stdin: &str) -> RunRequest {
    RunRequest { language: language.into(), code: code.into(), stdin: stdin.into() }
}

How do you run code you don't trust from Rust?#

Install the rustbox-sdk crate, create a Rustbox client with your API key and call run() with a RunRequest. The client sends the code, waits until the run has finished and returns a typed Execution. result.verdict is Some(Verdict::Accepted) when the program ran fine.

use rustbox_sdk::Verdict;

let execution = client.run(&request("javascript", "console.log(40 + 2)", "")).await?;

if execution.result.verdict == Some(Verdict::Accepted) {
    print!("{}", execution.output.stdout.unwrap_or_default()); // 42
}

How do you limit memory in Rustbox (cgroups v2)?#

You don't set memory per request. Each profile has a hard memory limit, 256 MB on the Judge profile, tied to your API key. Code that goes over it is stopped and gets an MLE (too much memory) result. You can see the limit on any result in the evidence.limits.cgroup block.

let execution = client
    .run(&request("python", "x = bytearray(512 * 1024 * 1024)  # exceeds the 256 MB limit", ""))
    .await?;

let evidence = serde_json::Value::Object(execution.evidence.unwrap_or_default());
let cgroup = &evidence["limits"]["cgroup"];
println!("verdict:   {:?}", execution.result.verdict);       // Some(MemoryLimit)
println!("limit:     {} bytes", cgroup["memory_limit_bytes"]); // 268435456 (256 MB)
println!("peak:      {} bytes", cgroup["memory_peak_bytes"]);
println!("oom_kills: {}", cgroup["oom_kill_events"]);        // >= 1

The MLE result comes from the system's own out-of-memory record, not a guess from the exit code. See Security for where memory limits sit among the 8 layers.

How do you give a program input (stdin)?#

Put the input in the stdin field of RunRequest (up to 256 KB). The program reads it as its normal input. What it prints comes back in output.stdout.

let execution = client
    .run(&request("python", "a, b = map(int, input().split())\nprint(a + b)", "5 10"))
    .await?;

println!("{:?}", execution.output.stdout); // Some("15\n")

How do you catch a too-slow (TLE) result in Rust?#

Send code that runs past the time limit (7 s for Python on Judge). Rustbox stops it and returns result.verdict as TLE, which the SDK exposes as Verdict::TimeLimit.

use rustbox_sdk::Verdict;

let execution = client.run(&request("python", "while True:\n    pass", "")).await?;

match execution.result.verdict {
    Some(Verdict::TimeLimit) => println!("timed out"),
    Some(other) => println!("finished with verdict {}", other.as_str()),
    None => println!("no verdict"),
}

How do you send code now and get the result later?#

Call submit(). You get back an Accepted with the run's id and status straight away. Then call get_result(id) until is_done() is true. You don't keep a connection open, which suits batches and webhook-based pipelines. Pass an idempotency key if you want the submit retried safely on network errors.

let code = "package main\nimport \"fmt\"\nfunc main() { fmt.Println(\"hi\") }";
let accepted = client.submit(&request("go", code, ""), None).await?;

loop {
    let execution = client.get_result(&accepted.id).await?;
    if execution.is_done() {
        println!("{:?}", execution.result.verdict);
        break;
    }
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;
}

How do you handle rate limits and temporary errors in the Rust SDK?#

The SDK retries server errors (5xx), timeouts and network failures on reads and idempotent submits by itself, waiting a little longer each time. Set how many retries with Options::max_retries. A 429 comes back as RustboxError::RateLimit with the server's Retry-After, and 401/403 come back as RustboxError::Auth. Match on the error to handle each case.

use rustbox_sdk::{Options, RustboxError};

let client = Rustbox::with_options(&api_key, Options { max_retries: 3, ..Options::default() })?;

match client.run(&request("python", "print(1)", "")).await {
    Ok(execution) => println!("{:?}", execution.result.verdict),
    Err(RustboxError::RateLimit { retry_after_secs }) => eprintln!("429, retry after {retry_after_secs:?}s"),
    Err(RustboxError::Auth { status }) => eprintln!("auth failed (HTTP {status}), check the API key"),
    Err(RustboxError::Server { status }) => eprintln!("HTTP {status} after the SDK's retries"),
    Err(RustboxError::Timeout(reason)) => eprintln!("timed out: {reason}"),
    Err(RustboxError::StillRunning { run_id }) => eprintln!("still running, fetch {run_id} later"),
    Err(other) => eprintln!("other error: {other}"),
}

How do you check that every protection was on for a run?#

Every finished result has an evidence.isolation block with controls_applied and controls_missing. Check that the protections you need, for example pid_namespace, network_namespace, memory_limit and no_new_privileges, are in controls_applied and that controls_missing is empty.

let execution = client.run(&request("python", "print(1)", "")).await?;
let evidence = serde_json::Value::Object(execution.evidence.unwrap_or_default());
let isolation = &evidence["isolation"];

let applied = isolation["controls_applied"].as_array().cloned().unwrap_or_default();
let required = ["pid_namespace", "network_namespace", "memory_limit", "no_new_privileges"];
let all_present = required.iter().all(|control| applied.iter().any(|a| a == control));

println!("isolation mode: {}", isolation["mode"]);              // "strict"
println!("all required controls applied: {all_present}");       // true
println!("controls missing: {}", isolation["controls_missing"]); // []

How do you list the languages Rustbox supports?#

Call get_languages(). It returns a Vec<String> with the name of every language this Rustbox runs. Use it to check input before sending, or to fill a language picker. The open beta supports 8 languages: C, C++, Go, Java, JavaScript, Python, Rust, and TypeScript.

let languages = client.get_languages().await?;
println!("{} languages: {:?}", languages.len(), languages);

let target = "rust";
if !languages.iter().any(|language| language == target) {
    eprintln!("{target} is not available on this instance");
}

Next steps#