Library / SDK
LaurentMazare/tch-rs avatar
LaurentMazare/tch-rs

tch-rs: Rust bindings for the PyTorch C++ API, and what they cost you

Rust bindings for the C++ api of PyTorch.

5,496 stars457 forksRustApache-2.0

At a glance

What is it?
The tch crate wraps libtorch v2.13.0 in thin Rust types instead of building a new tensor engine. That buys API fidelity and a hard dependency on matching C++ binaries, and it decides who should adopt it.
Who is it for?
Adopt tch-rs when you need libtorch semantics, TorchScript loading, or pretrained weights from Rust, and when pinning a libtorch build is acceptable. Do not adopt it if you want a pure-Rust dependency graph with no C++ toolchain, or if you need your code to compile identically on MinGW and MSVC Windows.
Can I use it commercially?
Yes. Apache-2.0 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 39 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

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

Editorial analysis

What tch-rs solves, and who actually needs it

The tch crate exists because PyTorch's real implementation is C++ and the Python package is one binding to it, not the library itself. tch-rs exposes that same C++ surface (libtorch) to Rust. The README states the goal plainly: "thin wrappers around the C++ PyTorch api", staying "as close as possible to the original C++ api", with more idiomatic bindings left for other crates to build on top.

That framing tells you who it is for. If you have a Rust service that must load an existing TorchScript module, or a Rust binary that needs the same operators and the same numerical behaviour as a PyTorch model trained elsewhere, tch-rs is the shortest path. You get Tensor, nn::VarStore, optimizers, and the rest of the libtorch vocabulary with Rust ownership around it.

It is a poor fit for someone who wants a self-contained Rust ML stack. The crate is not an independent implementation; it is a bridge, and every property of the underlying C++ library, including its build complexity, becomes yours.

How the binding layer is structured

The repository is a Cargo workspace with three members listed in Cargo.toml: torch-sys, pyo3-tch, and examples/python-extension. The split is the mechanism. torch-sys holds the low-level FFI against libtorch; tch builds the safe wrapper types on top of it. The src/ directory contains generated wrapper files, and the Makefile shows where they come from: a dune target runs gen/gen.exe and then rustfmt formats src/wrappers/tensor_generated.rs, src/wrappers/tensor_fallible_generated.rs, and torch-sys/src/c_generated.rs. The README notes the code generation for the C API comes from the author's ocaml-torch project.

So the data flow is: generated Rust declarations, a link step against libtorch through build.rs, and runtime calls that hand tensors across the FFI boundary. The build script is the part that matters operationally. It looks for libtorch in several places, in a documented order, and the choice you make there determines whether your binary links a system library, a downloaded one, or the one inside a Python install.

The thin-wrapper philosophy has a visible consequence: tch-rs does not hide libtorch's shape. If a function exists in C++ with awkward semantics, the Rust side tends to mirror it rather than paper over it. That is deliberate, and it means Rust ergonomics are not the crate's selling point.

Installing tch-rs and running your first tensor

The crate needs libtorch v2.13.0 on the system. The README lists four ways to satisfy that: a system-wide installation (the default), a manual install pointed at by the LIBTORCH environment variable, a Python PyTorch install via LIBTORCH_USE_PYTORCH=1, or the download-libtorch feature, which fetches a prebuilt binary when nothing else is found. On Linux the build script looks in /usr/lib/libtorch.so.

The manual route is the one most people end up on. After unzipping libtorch, you export the path:

bash
export LIBTORCH=/path/to/libtorch

The README also allows splitting headers from libraries, using LIBTORCH_INCLUDE (which must contain the include directory) and LIBTORCH_LIB (which must contain lib). On Windows the equivalent is a LIBTORCH environment variable pointing at the unzipped directory plus X:\path\to\libtorch\lib appended to Path, or in PowerShell:

powershell
$Env:LIBTORCH = "X:\path\to\libtorch"
$Env:Path += ";X:\path\to\libtorch\lib"

Once the library resolves, the README's own smoke test is an example binary:

bash
cargo run --example basics

If that runs, the smallest useful program is the tensor snippet from the README, which multiplies a slice-backed tensor by two and prints it:

rust
use tch::Tensor;

fn main() {
    let t = Tensor::from_slice(&[3, 1, 4, 1, 5]);
    let t = t * 2;
    t.print();
}

You should see the doubled values printed. If instead the build fails at link time, the problem is almost always that LIBTORCH points at a different libtorch version than v2.13.0, not that your Rust code is wrong.

Training loops, VarStore, and the optimizer step

The README's gradient descent example shows the actual training contract. Variables are created through nn::VarStore by declaring shapes and initializations; the example's my_module uses p.zeros("x1", &[dim]) and p.zeros("x2", &[dim]), so both start at zero. The forward pass is xs * &x1 + xs.exp() * &x2. An optimizer is built with nn::Sgd::default().build(&vs, 1e-2), and each step computes a loss, then calls opt.backward_step(&loss), which runs backpropagation and updates the variables held by the VarStore.

That is the whole pattern, and it is worth noticing how little tch-rs adds. There is no graph object, no session, no compile step. State lives in the VarStore, gradients are computed by libtorch's autograd, and the optimizer mutates the variables. The nn module also offers nn::seq() for stacking layers, as in the MNIST example where a linear layer is followed by a relu applied with add_fn.

Because the wrapper is thin, the mental model you need is PyTorch's, not Rust's. Anyone arriving from PyTorch will read this code quickly. Anyone expecting Rust to enforce something extra about training state will be disappointed; the safety the crate provides is about memory and ownership at the FFI boundary, not about model correctness.

Where tch-rs breaks: ABI, static linking and Windows

The most concrete limitation is version coupling. The README requires libtorch v2.13.0 specifically, and pyproject.toml pins torch==2.13.0 for the Python route. A libtorch of a different version is not a supported configuration, and the failure tends to surface as a link error or a crash rather than a clear message.

Windows carries an extra hazard the README calls out directly: per the PyTorch docs, Windows debug and release builds are not ABI-compatible, which "could lead to some segfaults if the incorrect version of libtorch is used". The same section recommends the MSVC Rust toolchain (for example stable-x86_64-pc-windows-msvc installed via rustup) over MinGW, citing compatibility issues between PyTorch and MinGW. If your team ships MinGW builds, this is a case where tch-rs is simply the wrong tool.

Static linking is possible via LIBTORCH_STATIC=1, but the README is honest that the prebuilt artifacts do not include libtorch.a by default, so you would have to build PyTorch yourself, for example by cloning the v2.13.0 tag with --recurse-submodule and running USE_CUDA=OFF BUILD_SHARED_LIBS=OFF python setup.py build. That is a large build to take on for a deployment convenience.

Finally, the thin-wrapper choice cuts both ways. Because tch-rs mirrors libtorch rather than abstracting it, it inherits libtorch's error behaviour and its operator set. If you want a Rust-native API with Rust-native error types, this crate will feel like a foreign object with a Rust signature.

tch-rs versus Burn and candle

The alternative people reach for is a pure-Rust stack, and the difference is architectural rather than cosmetic. Burn and candle implement their own tensor and kernel layers in Rust. That means no libtorch install, no LIBTORCH environment variable, no C++ toolchain in your build, and no ABI question on Windows. It also means the operator set, the backends, and the numerical behaviour are theirs to define, and loading a TorchScript file or matching a libtorch model exactly is not the same proposition.

tch-rs takes the opposite bet: reuse the C++ engine, accept the dependency. You get libtorch's operator coverage and its compatibility with the PyTorch ecosystem, including weights and traced modules, at the cost of a binary dependency that must be present and version-matched at build time.

The practical test is what you already have. If your models exist as TorchScript or as PyTorch checkpoints, and you want them running inside a Rust process, the binding approach avoids reimplementing anything. If your models are new and you are choosing a framework from scratch, a Rust-native engine removes an entire class of build problems. The repository's own examples directory is telling here: examples/jit, examples/jit-trace, examples/jit-quantized, and examples/jit-train are all about consuming or manipulating TorchScript artifacts, which is the work tch-rs is built for.

Maintenance, licensing and the upgrade path

The repository is not archived, and the last push was on 2026-08-23, so development is ongoing. The release history is sparse, though: the most recent release listed is tagged mw (model weights v0.1) from 2022-04-02, while Cargo.toml carries version 0.26.0. In practice the crate version in Cargo.toml and the git history are the things to follow, not the GitHub releases page. The README links a CHANGELOG.md at the repository root, which is where breaking changes should be recorded.

Upgrade cost is dominated by the libtorch version, not by the Rust API. Moving to a new libtorch means changing both the pinned C++ library and the torch-sys version that matches it, since torch-sys is a path dependency at the same 0.26.0 version. Every environment that builds the crate has to move at the same time: developer machines, CI, and any container images. The download-libtorch feature reduces that to a version bump, but only for CPU builds by default; the README notes TORCH_CUDA_VERSION can be set to cu117 for a prebuilt CUDA 11.7 binary.

On licensing, the crate declares MIT/Apache-2.0 in Cargo.toml and the repository ships LICENSE-MIT and LICENSE-APACHE. That covers the Rust code. libtorch is a separate artifact with its own terms, and the README does not discuss them, so anyone redistributing a linked binary needs to check the PyTorch licence independently. This is not legal advice, just a pointer to the boundary between the two licences.

Editorial conclusion

Adopt tch-rs when you need libtorch semantics, TorchScript loading, or pretrained weights from Rust, and when pinning a libtorch build is acceptable. Do not adopt it if you want a pure-Rust dependency graph with no C++ toolchain, or if you need your code to compile identically on MinGW and MSVC Windows. Before writing application code, verify three things: that your libtorch is exactly v2.13.0, that a trivial Tensor::from_slice program compiles and runs under cargo run --example basics, and that your Windows target is MSVC rather than MinGW. If the basics example fails, nothing above it will work.

Frequently asked questions

Is Rust basically C++?

No. tch-rs is a case in point: it is Rust code that links against a C++ library, and the two languages stay separate, with the FFI boundary handled by torch-sys. The crate's own goal is thin wrappers over the C++ PyTorch API, not a translation of C++ into Rust.

Is PyTorch just Python?

No. The implementation is the C++ library libtorch, and tch-rs binds to that C++ API directly. The README describes the crate as Rust bindings for the C++ api of PyTorch, which is why a libtorch installation is required.

Is Torch a C++ library?

The part tch-rs depends on is. The README requires the C++ PyTorch library (libtorch) in version v2.13.0 to be available on your system, and the crate wraps that C++ API.

Is Rust replacing C++ in 2026?

tch-rs does not settle that question either way, and its own design points the other direction: the crate links against libtorch v2.13.0, so the C++ library has to be present and version-matched for the Rust code to build at all.

Official sources

  1. Issues
  2. LaurentMazare/tch-rs on GitHub
  3. License: Apache-2.0
  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/laurentmazare-tch-rs.svg)](https://hysenlabs.com/projects/laurentmazare-tch-rs)