Railgun Rosetta — Cochran Block
Description: Railgun Rosetta. A developer's translation key between Python, C, and Rust. The railgun's native tongue is Rust; this is how you arrive from any other language.

CochranBlock
cochranblock.org/railgun-rosetta · the cochran block, llc

# RAILGUN ROSETTA

Translation key · Python · C · Rust
The railgun's native tongue is Rust. This is the key that gets you here from any other language. Start where you are. Translate one row at a time.

## Why this exists

Every Cochran Block product (runsible, r8r, exopack, others in the Manual) is shipped as a single Rust binary. Most working developers don't write Rust. They write Python, JavaScript, Go, C, Java, C#, Ruby. The Rosetta is the translation between the language you already know and the language the railgun speaks.

It is not a complete reference. It is a working alternative to "I'd love to use that crate but I don't know Rust." Read one row, copy the right column into your editor, ship.

Python C Go (coming) JavaScript / TS (coming) Java / Kotlin (coming) C++ (coming) C# (coming) Ruby (coming) Bash (coming)

## Python ↔ Rust

The first translation, because Michael's mental model is Python-procedural. Concurrency-focused, no OOP. Most rows here are 1-for-1.

Pattern | Python | Rust
--- | --- | ---
Print a line |
    print("hello, world")
    println!("hello, world");
Variable (immutable by default in Rust) |
    name = "Michael"
    age  = 38
    let name = "Michael";
    let age: u32 = 38;
Mutable variable |
    count = 0
    count += 1
    let mut count = 0;
    count += 1;
String formatting |
    msg = f"hi {name}, age {age}"
    let msg = format!("hi {name}, age {age}");
List / Vec |
    xs = [1, 2, 3]
    xs.append(4)
    for x in xs:
        print(x)
    let mut xs: Vec<i32> = vec![1, 2, 3];
    xs.push(4);
    for x in &xs {
        println!("{x}");
    }
Dict / HashMap |
    m = {"a": 1, "b": 2}
    m["c"] = 3
    for k, v in m.items():
        print(k, v)
    use std::collections::HashMap;
    let mut m: HashMap<&str, i32> = HashMap::new();
    m.insert("a", 1);
    m.insert("b", 2);
    m.insert("c", 3);
    for (k, v) in &m {
        println!("{k} {v}");
    }
Function |
    def add(a: int, b: int) -> int:
        return a + b
    fn add(a: i32, b: i32) -> i32 {
        a + b
    }
Error handling |
    try:
        result = risky()
    except IOError as e:
        print(f"oops: {e}")
    match risky() {
        Ok(result) => { /* use result */ }
        Err(e)     => eprintln!("oops: {e}"),
    }
    // or with ?:
    let result = risky()?;
Read a file |
    with open("notes.txt") as f:
        text = f.read()
    let text = std::fs::read_to_string("notes.txt")?;
Write a file |
    with open("out.txt", "w") as f:
        f.write("done\n")
    std::fs::write("out.txt", "done\n")?;
JSON parse |
    import json
    data = json.loads(s)
    name = data["name"]
    // Cargo.toml: serde_json = "1"
    let data: serde_json::Value =
        serde_json::from_str(&s)?;
    let name = data["name"].as_str().unwrap();
JSON struct (typed) |
    from dataclasses import dataclass
    @dataclass
    class User:
        name: str
        age:  int

    import json
    u = User(**json.loads(s))
    // Cargo.toml: serde = { version="1", features=["derive"] }
    //             serde_json = "1"
    use serde::Deserialize;
    #[derive(Deserialize)]
    struct User { name: String, age: u32 }
    let u: User = serde_json::from_str(&s)?;
HTTP GET |
    import requests
    r = requests.get("https://api.example.com/x")
    data = r.json()
    // Cargo.toml: reqwest = { version="0.12", features=["json"] }
    let data: serde_json::Value =
        reqwest::blocking::get("https://api.example.com/x")?.json()?;
HTTP server (FastAPI ↔ axum) |
    from fastapi import FastAPI
    app = FastAPI()

    @app.get("/hello")
    def hello():
        return {"msg": "hi"}
    // Cargo.toml: axum = "0.8", tokio = { version="1", features=["full"] }
    use axum::{Router, routing::get, Json};
    use serde_json::json;

    #[tokio::main]
    async fn main() {
        let app = Router::new()
            .route("/hello", get(|| async { Json(json!({"msg": "hi"})) }));
        let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
        axum::serve(listener, app).await.unwrap();
    }
Async coroutine |
    import asyncio

    async def fetch(url):
        await asyncio.sleep(1)
        return url

    asyncio.run(fetch("x"))
    // Cargo.toml: tokio = { version="1", features=["full"] }
    async fn fetch(url: &str) -> &str {
        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
        url
    }

    #[tokio::main]
    async fn main() {
        let _ = fetch("x").await;
    }
Run a subprocess |
    import subprocess
    out = subprocess.run(
        ["ls", "-la"],
        capture_output=True, text=True
    ).stdout
    let out = std::process::Command::new("ls")
        .arg("-la")
        .output()?;
    let stdout = String::from_utf8(out.stdout)?;
Regex |
    import re
    m = re.match(r"(\d+)-(\w+)", "42-foo")
    if m: print(m.group(1), m.group(2))
    // Cargo.toml: regex = "1"
    use regex::Regex;
    let re = Regex::new(r"(\d+)-(\w+)")?;
    if let Some(c) = re.captures("42-foo") {
        println!("{} {}", &c[1], &c[2]);
    }
CLI args |
    import argparse
    p = argparse.ArgumentParser()
    p.add_argument("--name", required=True)
    args = p.parse_args()
    print(args.name)
    // Cargo.toml: clap = { version="4", features=["derive"] }
    use clap::Parser;
    #[derive(Parser)]
    struct Args { #[arg(long)] name: String }
    fn main() {
        let args = Args::parse();
        println!("{}", args.name);
    }
Test |
    def test_add():
        assert add(2, 3) == 5

    # pytest tests/test_x.py
    #[test]
    fn test_add() {
        assert_eq!(add(2, 3), 5);
    }

    // cargo test

Common pivots from Python. Three concepts that change shape on the way to Rust: ownership (Python garbage collects, Rust tracks who owns each value), match (Rust's pattern match replaces Python's if/elif chains and structural unpacking), and cargo (one tool replaces pip + venv + setuptools + pytest + tox). None of these require new mental models so much as new muscle memory.

## C ↔ Rust

For the systems crowd. Rust keeps everything C gives you (no GC, predictable layout, FFI), and adds the ownership rules that make refactoring safe.

Pattern | C | Rust
--- | --- | ---
Print a line |
    #include <stdio.h>
    printf("hello, world\n");
    println!("hello, world");
Heap allocation |
    int *p = malloc(sizeof(int));
    *p = 42;
    free(p);
    let p = Box::new(42_i32);
    // dropped automatically at end of scope
Pointer / reference |
    void incr(int *x) { (*x)++; }
    int v = 1;
    incr(&v);
    fn incr(x: &mut i32) { *x += 1; }
    let mut v = 1;
    incr(&mut v);
Strings |
    const char *name = "Michael";
    char buf[64];
    snprintf(buf, sizeof buf,
             "hi %s", name);
    let name = "Michael";
    let buf  = format!("hi {name}");
Struct |
    struct Point { float x, y; };
    struct Point p = { .x = 1.0, .y = 2.0 };
    struct Point { x: f32, y: f32 }
    let p = Point { x: 1.0, y: 2.0 };
Enum / tagged union |
    enum Shape { CIRCLE, SQUARE };
    struct ShapeVal {
        enum Shape kind;
        union { float r; float side; };
    };
    enum Shape {
        Circle { r: f32 },
        Square { side: f32 },
    }
Read a file |
    FILE *f = fopen("notes.txt", "r");
    char buf[1024];
    size_t n = fread(buf, 1, 1023, f);
    buf[n] = '\0';
    fclose(f);
    let text = std::fs::read_to_string("notes.txt")?;
Dynamic array |
    int *xs = malloc(4 * sizeof(int));
    xs[0] = 1; xs[1] = 2; xs[2] = 3; xs[3] = 4;
    // realloc, track length, free, etc.
    free(xs);
    let xs = vec![1, 2, 3, 4];
    // length tracked, dropped automatically
Error handling |
    int rc = do_thing();
    if (rc != 0) {
        fprintf(stderr, "fail: %s\n", strerror(errno));
        return rc;
    }
    do_thing()?;
    // or with full match:
    match do_thing() {
        Ok(_)  => {},
        Err(e) => eprintln!("fail: {e}"),
    }
Function pointer / closure |
    typedef int (*Fn)(int);
    int apply(int x, Fn f) { return f(x); }
    int sq(int x) { return x * x; }
    int y = apply(3, sq);
    fn apply<F: Fn(i32) -> i32>(x: i32, f: F) -> i32 { f(x) }
    let y = apply(3, |n| n * n);
Build & run |
    cc -O2 -o app main.c
    ./app
    cargo run --release
Static link to libc / FFI |
    // you ARE libc
    extern int getpid(void);
    extern "C" { fn getpid() -> i32; }
    unsafe { let _ = getpid(); }

The big swap from C. You stop writing memory plumbing. `Box`, `Vec`, `String`, `Result`, and the borrow checker take over the work that used to be malloc/realloc/free + length tracking + error codes + segfault hunts. Most C programs become shorter in Rust.

## Coming next

Translations are added in a deliberate order: Python first (above), then C (above), then Go, JavaScript / TypeScript, Java / Kotlin, C++, C#, Ruby, Bash. Each language gets the same row pattern so your eye learns the shape once.

Source for this page lives in the cochranblock crate at github.com/cochranblock/cochranblock. Pull requests adding rows or whole languages are welcome and will be folded into the next manual revision.

The Cochran Block, LLC · EIN 41-3835237 · CAGE 1CQ66 · UEI W7X3HAQL9CF9 · SDVOSB Pending · Maryland CSB Approved
Home · Manual · crates.io · docs.rs · GitHub · Email