Jump to content

Accelerating Python with WebAssembly and Rust Extensions

From JOHNWICK

For years, I’ve loved Python for its expressiveness but hated its performance bottlenecks. Anyone who has tried crunching large datasets, running tight loops, or building high-performance services knows the feeling: Python is elegant, but slow. That’s when I started experimenting with two powerful accelerators: WebAssembly (WASM) and Rust extensions. Both unlock insane performance gains while letting me keep Python as the “glue” language. This post is my field report.


1. Why Python Alone Isn’t Enough Python’s biggest weakness is its Global Interpreter Lock (GIL) and reliance on CPython. Tight loops and CPU-heavy tasks choke quickly.

  1. Naive Fibonacci in Python

def fib(n):

   if n <= 1:
       return n
   return fib(n - 1) + fib(n - 2)

print(fib(35)) # painfully slow This call to fib(35) takes seconds in Python. In Rust or compiled WASM, it runs in milliseconds.


2. Enter WebAssembly (WASM) WASM was designed for the web but has grown into a universal runtime. It runs fast, secure, and portable. Embedding WASM into Python means I can write performance-sensitive logic in a compiled language but call it like a Python function. Let’s compile a Rust function into WASM and use it in Python. // fib.rs

  1. [no_mangle]

pub extern "C" fn fib(n: u32) -> u32 {

   match n {
       0 => 0,
       1 => 1,
       _ => fib(n - 1) + fib(n - 2),
   }

} Compile to WASM: rustc --target wasm32-unknown-unknown -O fib.rs -o fib.wasm Load in Python: import wasmtime

store = wasmtime.Store() module = wasmtime.Module.from_file(store.engine, "fib.wasm") instance = wasmtime.Instance(store, module, [])

fib = instance.exports(store)["fib"] print(fib(store, 35)) # runs way faster Suddenly, Python isn’t slow anymore.


3. Rust Extensions: The Direct Power Boost If WASM is about portability, Rust extensions are about raw power. With PyO3 or maturin, you can build native Rust modules that import directly into Python. // lib.rs use pyo3::prelude::*;

  1. [pyfunction]

fn fib(n: u32) -> u32 {

   match n {
       0 => 0,
       1 => 1,
       _ => fib(n - 1) + fib(n - 2),
   }

}

  1. [pymodule]

fn fastfib(_py: Python, m: &PyModule) -> PyResult<()> {

   m.add_function(wrap_pyfunction!(fib, m)?)?;
   Ok(())

} Build with maturin: maturin build --release Then in Python: import fastfib print(fastfib.fib(35)) # blazingly fast This approach gave me near-C performance without leaving Python behind.


4. When to Use WASM vs Rust Extensions Through trial and error, I discovered this simple rule of thumb:

  • Use WASM → when you need portability (run the same binary on web, server, and edge).
  • Use Rust extensions → when you just need Python to run faster on your local or server environment.

Both approaches can coexist in the same codebase.


5. Data Processing at Scale To stress-test, I ported a text processing function from Python to Rust and benchmarked. Python: def word_count(texts):

   counts = {}
   for text in texts:
       for word in text.split():
           counts[word] = counts.get(word, 0) + 1
   return counts

Rust: use std::collections::HashMap;

  1. [pyfunction]

fn word_count(texts: Vec<String>) -> HashMap<String, u32> {

   let mut counts = HashMap::new();
   for text in texts {
       for word in text.split_whitespace() {
           *counts.entry(word.to_string()).or_insert(0) += 1;
       }
   }
   counts

} Rust crushed Python by more than 50x on my test dataset of 10M sentences.


6. Combining WASM and Rust in a Python Workflow My favorite setup: heavy compute in Rust → compiled to WASM → orchestrated in Python. This way, I can run the same logic everywhere, even inside a browser UI. from wasmtime import Store, Module, Instance

store = Store() module = Module.from_file(store.engine, "wordcount.wasm") instance = Instance(store, module, [])

word_count = instance.exports(store)["word_count"] result = word_count(store, "Python Rust WASM Python") print(result) This flexibility is a game changer for hybrid web + backend projects.


7. Error Handling and Debugging Debugging WASM and Rust extensions took some time. Here’s my pattern:

  • Write tests in Rust before exposing functions.
  • Add fallbacks in Python in case the extension crashes.

try:

   result = fastfib.fib(100000)

except Exception as e:

   print(f"Fallback to Python: {e}")
   result = fib_py(100000)

This way, the system never completely breaks.


8. Deploying in Production For production, I containerized my Python+Rust hybrid app. FROM python:3.11-slim

RUN apt-get update && apt-get install -y build-essential rustc cargo

WORKDIR /app COPY . . RUN pip install maturin && maturin build --release

CMD ["python", "main.py"] This allowed my services to run at near-Rust speed but still integrate seamlessly with Python frameworks like FastAPI and Pandas.


9. Final Thoughts Python is never going to match C or Rust in raw performance. But with WASM and Rust extensions, it doesn’t need to. You get the best of both worlds: Python’s elegance + Rust’s power. For me, this hybrid approach turned Python from a bottleneck into a performance powerhouse. As Donald Knuth said: “Premature optimization is the root of all evil.” But with Rust and WASM in Python, it doesn’t feel like premature optimization — it feels like superpowers.

Read the full article here: https://medium.com/@maximilianoliver25/accelerating-python-with-webassembly-and-rust-extensions-f9b1182c152b