Framework
rust-ml/linfa avatar
rust-ml/linfa

Linfa: a scikit-learn-shaped toolkit for classical ML in Rust

A Rust machine learning framework.

4,749 stars334 forksRustApache-2.0

At a glance

What is it?
Linfa splits classical machine learning into per-algorithm crates that share one dataset and trait model, with a pure-Rust linear algebra default and optional BLAS/LAPACK backends. The design is coherent; the cost is that you assemble the stack yourself and pin versions across a family of crates.
Who is it for?
Adopt Linfa if you are building a Rust binary or service that needs classical estimators (linear and logistic regression, SVM, k-means, PCA, t-SNE, decision trees) without a Python runtime, and you accept that you will select and pin each algorithm crate yourself.
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 24 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 September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What Linfa replaces, and for whom

The README describes Linfa as aiming "to provide a comprehensive toolkit to build Machine Learning applications with Rust" and places it "kin in spirit to Python's scikit-learn", focused on "common preprocessing tasks and classical ML algorithms". That sentence is the whole scope statement. Linfa is not a deep learning framework and does not pretend to be one. It targets the part of a Python workflow that is usually scikit-learn: scale the features, fit a model, predict, score.

The audience follows from that. If you are writing a Rust service that already has a feature pipeline in Rust, pulling in Python or shelling out to a Python process for a logistic regression or a k-means pass is friction. Linfa is for that case. It is also for embedded and browser targets, since the README documents a wasm32-unknown-unknown path. It is not for someone who wants one import that gives them everything, because the project is explicitly a set of sub-packages rather than a monolith.

One crate per algorithm family, one shared dataset model

The architecture visible in the README is a table of sub-packages, each in its own directory under algorithms/. There are eighteen rows: bayes, clustering, ensemble, elasticnet, ftrl, hierarchical, ica, kernel, lars, linear, logistic, nn, pls, preprocessing, reduction, svm, trees and tsne. Each row carries a purpose, a status, a category and notes. Clustering, for example, is listed as "Tested / Benchmarked" and contains "K-Means, Gaussian-Mixture-Model, DBSCAN and OPTICS".

The categories in that table tell you how the pieces are meant to compose: pre-processing (kernel, nn, preprocessing, reduction), supervised learning (bayes, ensemble, elasticnet, lars, linear, svm, trees), unsupervised learning (clustering, hierarchical, ica, tsne), and partial fit (ftrl, logistic). The partial-fit category is the interesting one, because it implies an incremental update path rather than a one-shot fit, and the ftrl notes say "Possible incremental update".

What the README does not show is the trait definitions that let these crates interoperate. The description calls Linfa a framework, and the shared dataset and estimator traits are the reason a per-algorithm split is workable at all, but the supplied material does not include them. Treat the composition story as documented-by-implication rather than documented-in-detail.

Two feature flags decide your build: wasm-bindgen and blas

The README documents two build-time switches, and both change the dependency graph rather than a runtime option.

The first is for browsers. "For browser-style WASM on wasm32-unknown-unknown, enable linfa's wasm-bindgen feature." The phrasing is narrow on purpose: this is browser-style WASM on that specific target triple, not WASM generally.

The second is linear algebra. "Some algorithm crates need to use an external library for linear algebra routines. By default, we use a pure-Rust implementation." To switch, you enable the blas feature plus a backend feature. The README names three: openblas, netblas and intel-mkl. There is a backend-versus-platform table in the README, but the supplied copy is truncated mid-table, so the per-platform support matrix cannot be stated here.

This is the design decision with the widest blast radius. The pure-Rust default means a plain cargo build works without a system BLAS, which matters for CI containers and cross-compilation. Enabling blas trades that for a native library you now have to install and link. In Cargo terms it looks roughly like adding linfa with default features off and features = ["blas", "openblas"], but the exact feature names and which algorithm crates require the backend are not fully specified in the material provided, so confirm against the crate you are actually pulling in.

Getting it running, and what the README leaves open

The material shows the crate name (linfa on crates.io) and the feature names, but no install snippet. The commands below follow from that and from standard Cargo usage, not from a quoted example in the README.

cargo add linfa

cargo add linfa --features wasm-bindgen

cargo add linfa --features blas,openblas

The second and third lines are the two documented feature switches. Everything else about setup is unstated. The README does not give a first-fit example, does not show how a dataset is constructed, and does not show how an estimator is trained or scored. The homepage field in the repository metadata is empty, and the README points instead to a website at rust-ml.github.io/linfa and a Zulip chat at rust-ml.zulipchat.com. The docs.rs badge and the docs-latest badge suggest the API reference is the place to look, but that reference is outside the material here.

So the honest summary of onboarding is: the feature story is documented, the API story is not, and you will be reading rustdoc before you write your first fit call.

The version-pinning cost of eighteen crates

Releases in the material are 0.8.1 (2025-12-23), 0.8.0 (2025-09-30) and 0.7.1 (2025-01-17). The last push to master is 2026-08-22, which is later than the newest release, so development continues between releases.

The upgrade cost here is not a single version bump. If you use four algorithm crates, you have four version constraints plus the umbrella linfa crate, and they have to agree. In Rust, a 0.x to 0.y change is conventionally a breaking change, so 0.7.1 to 0.8.0 is not a patch you apply casually across a dependency tree. The README's own table marks status per crate ("Tested" for most, "Tested / Benchmarked" for clustering, ftrl, nn, preprocessing and trees), which hints that crates do not all move in lockstep, though the material does not confirm independent release cadences.

There is also a maintenance dimension the README states directly: "We believe that only a significant community effort can nurture, build, and sustain a machine learning ecosystem in Rust - there is no other way forward." That is the project describing its own resourcing as community-dependent and pointing readers at a roadmap issue. Plan for a dependency you may need to contribute to, not one you can treat as a black box.

Where Linfa is the wrong tool

Three cases stand out.

First, anything that is not classical ML. The README's scope is preprocessing plus classical algorithms. There is no neural network crate in the table. If your problem is sequence modelling, embeddings or large-scale vision, Linfa has nothing to offer and you should not try to bend a linear model into that shape.

Second, anything that needs a stable estimator surface across upgrades. The release history shows a 0.8.0 minor bump in late 2025. If your codebase cannot absorb a breaking change in a 0.x dependency on someone else's schedule, a per-algorithm crate family is a poor fit, because the surface you depend on is spread across many crates.

Third, the case where you need a BLAS-backed routine on a platform the feature table does not cover. The README's backend table is truncated in the material provided, so a reader cannot confirm from this text whether intel-mkl or netblas is supported on their target. The default pure-Rust path sidesteps this entirely, at whatever performance cost the pure-Rust implementation carries. The README does not quantify that cost, and no benchmark numbers appear in the material, so treat "pure-Rust by default" as a build-simplicity choice, not a performance claim in either direction.

The alternative: scikit-learn, and the actual difference

The README itself names the comparison: Linfa is "kin in spirit to Python's scikit-learn". The difference is not the algorithm list, which overlaps heavily (naive Bayes, k-means, SVM, PCA, elastic net, decision trees all appear in both). The difference is the runtime and the dependency model.

scikit-learn gives you one import and a documented estimator interface, and it brings a Python interpreter, NumPy and SciPy with it. Linfa gives you a Rust library with no interpreter, and it brings a set of crates you select individually. If your application is already Rust, Linfa removes a process boundary. If your application is Python, or your team's model development happens in notebooks, scikit-learn keeps you in one language and one ecosystem.

A second alternative worth naming is exporting a trained model and running inference in Rust without a training library at all, for example via ONNX. That path is not mentioned in the material, so it is offered only as a direction to evaluate, not as something Linfa documents. The distinction matters: Linfa trains in Rust, whereas the export approach trains elsewhere and only executes in Rust. If you need incremental fitting or in-process preprocessing, export alone will not cover it.

Licence and the shape of adoption

The repository is Apache-2.0. That is a permissive licence, and it is the same licence family used across much of the Rust ecosystem, which keeps licence compatibility checks simple when you mix Linfa crates with other dependencies. This is a description of the licence identifier in the repository metadata, not legal advice; if you ship a product, your own counsel decides what obligations apply, and the Apache-2.0 text governs rather than this paragraph.

One thing the material does not resolve is whether every algorithm sub-crate carries the same licence. The repository-level licence is Apache-2.0, and per-crate licence files are not shown here, so verify that per crate if your policy requires it.

On adoption shape: Linfa is a reasonable choice for a Rust codebase that needs a handful of classical estimators and can tolerate 0.x version churn. It is a poor choice as a first machine learning dependency for someone still deciding what to build, because the README documents features and a crate table rather than a worked example, and the API reference lives outside this material. The concrete next step is to pick your two or three algorithm crates from the table, check each one's published version against linfa 0.8.1, and build a throwaway binary with the default features before you try the blas or wasm-bindgen paths. If that build succeeds and the rustdoc traits match your data, the rest of the decision is about who maintains the crates you picked.

Editorial conclusion

Adopt Linfa if you are building a Rust binary or service that needs classical estimators (linear and logistic regression, SVM, k-means, PCA, t-SNE, decision trees) without a Python runtime, and you accept that you will select and pin each algorithm crate yourself. Do not adopt it if you need deep learning, a single batteries-included dependency, or a stable estimator API across minor versions; the 0.8.0 release in this material is a minor bump, which in Rust conventionally permits breaking changes. Before committing, verify three things: which algorithm crates you actually need and whether each has been released at a version compatible with linfa 0.8.1, whether your target triple builds against the default pure-Rust backend or requires the blas feature plus openblas, netblas or intel-mkl, and whether your deployment target is wasm32-unknown-unknown, in which case the wasm-bindgen feature has to be enabled. The last item is the one most likely to force a redesign, because it changes the feature set of the whole dependency graph rather than one crate.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. Releases
  5. rust-ml/linfa on GitHub
Community notes

Community notes