Library / SDK
py2many/py2many avatar
py2many/py2many

py2many: a Python transpiler where Rust is the finished language and the rest is not

Transpiler of Python to many other languages

1,337 stars91 forksPythonMIT

At a glance

What is it?
py2many converts Python source into Rust, C++, Go, Zig, Mojo, Julia, Nim, Dart, Kotlin, D, V and Lean, with per language packages, a tree-sitter based front end and a status table that grades each target. The status table is the important part: Python to Rust is the primary focus, C++ is production ready, and everything else is beta or experimental.
Who is it for?
py2many fits a developer who prototypes in Python and then needs a statically typed version of one specific function for a hot path, an embedded target or a WebAssembly build, and who is willing to treat the generated code as a starting point rather than a finished artifact. It does not fit a team that wants to convert an entire Python codebase to another language and maintain it there, and it does not fit a team that has not decided which target language it wants.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The status table is the documentation that matters

A transpiler that claims twelve target languages is usually a project where one target works and the rest are demonstrations. py2many says so explicitly, and the README links a status file, LANGUAGES.md, for the current status of each language.

The graded list in the README is unambiguous. The primary focus is Python to Rust conversion, with the most mature feature set and active development. Production ready is Python to C++, where C++14 was historically supported and C++17 or later is required for advanced features. Beta support covers Python to Lean, Julia, Kotlin, Nim, Go, Dart, V and D. Experimental support is Python to Zig and Python to Mojo, at an early stage with limited feature coverage.

That grading is the most valuable thing in the project for an adopter, because it converts a vague claim into a decision. If you need Rust, this is a tool with a real investment behind it. If you need Go, you are in the beta group and should expect rough edges. If you need Zig, you are looking at an early stage.

There is also a capability beyond transpilation. py2many can also emit enhanced Python 3 code with inferred type annotations and syntax improvements for better code analysis, which is a much smaller and much safer claim than the cross language work, and it is the feature you would reach for if you wanted type hints without running a separate type inference pass over your project.

Python to Rust, and the integer width problem it exposes

The worked example in the README is the clearest evidence of what the transpiler actually does, and it is worth reading closely because it shows a real semantic decision rather than a syntax translation.

The input is ordinary Python with a type annotation.

python
def fib(i: int) -> int:
    if i == 0 or i == 1:
        return 1
    return fib(i - 1) + fib(i - 2)

# Demonstrate overflow handling
def add(i: i32, j: i32):
    return i + j

And the Rust output is this.

rust
fn fib(i: i32) -> i32 {
    if i == 0 || i == 1 {
        return 1;
    }
    return (fib((i - 1)) + fib((i - 2)));
}

// return type is i64
pub fn add(i: i32, j: i32) -> i64 {
    return ((i as i64) + (j as i64)) as i64;
}

Two things stand out. The Python int maps to Rust i32, not to the platform-sized isize, and the example deliberately includes a function that takes two i32 arguments and returns an i64, with the comment that the return type is i64. The generator has made a decision about integer widths that the Python source did not express, and the comment in the generated code is the tool telling you what it decided.

That is the honest core of the problem. Python integers are arbitrary precision, and every statically typed target has to pick a width. A transpiler that picks silently produces code that overflows where Python would not have, and py2many at least annotates the choice in the output.

The redundant parentheses are the other visible artefact. Rust does not need them, and their presence tells you the code is generated by a tree walker that emits every operation as a parenthesised node rather than by a Rust-aware printer. That is a cosmetic issue, and it is also a hint about how much target language knowledge is in the pipeline.

One package per target language, wired through a shared core

The repository layout is a monorepo of per language backends, and it maps directly onto the package structure in the distribution metadata.

The top level directories are pyrs/, pycpp/, pyd/, pydart/, pygo/, pyjl/, pykt/, pylean/, pymojo/, pynim/, pyv/, pyzig/ and pysmt/, alongside the shared core in py2many/, the CLI entry point py2many.py, and the pyproject.toml, setup.py and setup.cfg.

The packaging metadata lists the same split as importable subpackages, py2many.pyrs, py2many.pycpp, py2many.pygo and so on, and the package_dir mapping sends each of those names to its own top level directory. The effect is that the wheel installs one py2many package with the language backends underneath it.

The core is not a tiny dispatcher. The core package itself is a full backend, py2many is the package that holds the shared machinery, and the shared machinery includes the type inference and the AST handling.

The runtime dependencies are short and reveal the architecture. argparse_dataclass, plus tree-sitter, tree-sitter-cpp and tree-sitter-rust. The tree-sitter grammars are only for C++ and Rust, which tells you something important: those two backends parse the target language rather than generating text blindly, they can read existing C++ and Rust files, and they are the two the project considers mature. The other backends work from the Python AST alone.

That is a sensible design. The languages the project cares about most are the ones it can round trip, read the target back, and use that to guide generation.

Running it, and the two ways to invoke the tool

The requirements are Python 3.8 or later according to the README quick start, and the pyproject.toml asks for requires-python 3.10 or newer, with setup.py still saying 3.8, a mismatch worth knowing about if you are on 3.9.

Installation is one command with uv.

sh
uv pip install py2many

Or you can run it without installing anything at all, using uvx.

sh
uvx py2many --mylang test.py

That second form is the one to reach for first. Because the tool is a pure command line program with a single entry point, py2many.cli:main, and because the project publishes expected outputs for every language under tests/expected, you can transpile a file and diff it against a known result without installing anything into your environment.

The per language flags follow the target name, and the general form is the same for all of them.

sh
# Python to Rust
py2many --rust tests/cases/fib.py

# Python to C++
py2many --cpp tests/cases/fib.py

The same file, fib.py, is used for every example, which is a sign of how the project is tested. The expected outputs are in the repository at tests/expected, with the fib files covering every language, and the README points you there for more examples.

The project also documents how to compile and run what it produced, which tells you the output is meant to be real code rather than a sketch.

sh
# Compile C++
clang tests/expected/fib.cpp

# Run Rust
./scripts/rust-runner.sh run tests/expected/fib.rs

For each target language, the setup instructions for the formatters and standard libraries are in the continuous integration workflow file, .github/workflows/main.yml, and the README notes the integration with rustfmt for Rust code formatting and the language specific standard libraries and external dependencies.

What a transpiler can and cannot do for you

The use cases in the README are wide, and they range from credible to optimistic in a way that is worth sorting.

The credible ones are narrow and specific. Transpile a Python prototype into Rust or C++ for speed. Turn a Python prototype into a systems language. Transpile Python to Rust or Zig for a WebAssembly deployment. Convert Python code to C++ or Rust for resource constrained environments. Transpile Python to Dart, which can generate JavaScript, for a web target.

All of those share a property: they are about one function or one algorithm, and they are about the target language's performance or deployment characteristics rather than about rewriting an application.

The optimistic one is cross platform libraries, writing once in Python and deploying to multiple language ecosystems. That is a much harder problem than the transpilation itself, because it requires that the Python subset you wrote maps identically onto every target's semantics, and that any library you call exists in all of them. The README is more careful about the mobile case, saying that while Swift and Kotlin dominate mobile app development there is no universal solution for sharing lower level library code between platforms, and that py2many provides an alternative to Kotlin Multiplatform by letting you write once in Python and deploy to multiple targets.

The limitation to keep in mind is the one inherent to the approach. A transpiler works on a subset of the source language, and Python is a large language with a lot of dynamic behaviour. The features that survive the crossing are the ones the backends have implemented, and the ones that do not are the ones you discover when the generated code does not compile or does not behave the same way.

That is why the project publishes expected outputs per language, and why the security argument in the README is framed the way it is: write security sensitive code in Python, verify it with unit tests, then transpile to a safer systems language like Rust. The verification step is not optional in that workflow, because the unit tests are testing the Python, not the Rust the transpiler produced from it.

Editorial conclusion

py2many fits a developer who prototypes in Python and then needs a statically typed version of one specific function for a hot path, an embedded target or a WebAssembly build, and who is willing to treat the generated code as a starting point rather than a finished artifact. It does not fit a team that wants to convert an entire Python codebase to another language and maintain it there, and it does not fit a team that has not decided which target language it wants. Check three things before you commit. Check the status table, because LANGUAGES.md grades every target and the differences are large, with Rust the primary focus and the most mature feature set, C++ production ready, a group of beta targets, and Zig and Mojo experimental with limited feature coverage. Check that your Python is a subset py2many understands, because the transpiler reasons over the AST and the tree-sitter dependencies are for the C++ and Rust backends. And check the version, because the project moved from 0.6 to 0.9 in the release list and the packages are per language. The last push was on 2026-10-07, the newest release listed is v0.9 from 2026-08-04, and the license is MIT.

Frequently asked questions

What is py2many and which languages does it target?

It is a Python transpiler that converts Python source into multiple statically typed languages, including Rust, C++, Go, Zig, Mojo, Julia, Nim, Dart, Kotlin, D, V and Lean. It can also emit enhanced Python 3 with inferred type annotations.

Which py2many targets are production ready?

The README grades Python to Rust as the primary focus with the most mature feature set, and Python to C++ as production ready, where C++17 or later is required for advanced features. Lean, Julia, Kotlin, Nim, Go, Dart, V and D are beta, and Zig and Mojo are experimental with limited feature coverage. LANGUAGES.md holds the current status of each language.

How do I install and run py2many?

Install with uv pip install py2many, or run it without installing using uvx py2many --mylang test.py. Then pick a target flag such as py2many --rust tests/cases/fib.py or py2many --cpp tests/cases/fib.py, and check the expected outputs under tests/expected.

Why does py2many depend on tree-sitter grammars for C++ and Rust?

The project lists tree-sitter, tree-sitter-cpp and tree-sitter-rust as its dependencies, and the grammars are only for those two languages. That suggests the C++ and Rust backends parse the target language, which is consistent with them being the most mature targets.

What does py2many do about Python integer widths in the generated code?

The README example shows a Python int mapped to Rust i32, and a function taking two i32 arguments returning an i64, with the generated code commenting that the return type is i64. The tool annotates the width it chose in the output.

What license is py2many released under?

MIT, as declared in both pyproject.toml and setup.py, with a LICENSE file at the root of the repository. The newest listed release is v0.9 from 2026-08-04, and the last push was on 2026-10-07.

Official sources

  1. Issues
  2. License: MIT
  3. py2many/py2many on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/py2many-py2many.svg)](https://hysenlabs.com/projects/py2many-py2many)

Was this useful?