tokio-modbus speaks Modbus TCP and RTU from async Rust
A tokio-based modbus library
At a glance
- What is it?
- tokio-modbus is a pure Rust Modbus library built on tokio, offering client and server implementations over TCP and RTU with async and sync APIs behind cargo features. It suits a Rust team talking to industrial devices or PLCs, and it requires a Rust toolchain and a device that speaks Modbus.
- Who is it for?
- Adopt tokio-modbus if you are writing Rust against Modbus devices and want the crate to fit an existing tokio service rather than sit beside it with its own thread pool. Do not adopt it if you need a Modbus implementation in a language your team does not use, since the whole library is Rust, and do not adopt it expecting TLS-secured or high-level industrial protocols beyond what Modbus itself offers.
- 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 93 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 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Modbus without a C runtime and without a thread per device
tokio-modbus is a pure Rust Modbus library based on tokio, and the pure part is a genuine constraint rather than a marketing word. There is no libmodbus underneath, no C library to cross compile, and no FFI boundary, which for a library that is going to run on a Raspberry Pi or an embedded Linux box alongside a tokio service is the whole point. The project is maintained by slowtec GmbH, the licence is MIT or Apache-2.0, and the primary language is Rust. The second design decision is the naming. Modbus is based on a master/slave communication pattern, and the README is explicit that to avoid confusion with tokio terminology, the master is called the client and the slave is called the server in this library. That is a small courtesy with a large practical effect, because the vocabulary of the crate matches the vocabulary of the runtime it is built on, and because a lot of Modbus documentation uses the older terms. The repository carries a security audit badge and a continuous integration badge, and the current crate version is 0.17.0 with edition 2024 and a minimum Rust version of 1.85.
The cargo feature matrix, and why the defaults are narrow
The feature list is the most important part of the documentation for anyone choosing this crate, because the default set is deliberately small. The rtu feature gives you the asynchronous RTU client and is on by default, and the tcp feature gives you the asynchronous TCP client and is also on by default. Everything else is opt-in: rtu-sync and tcp-sync for the synchronous blocking clients, rtu-server for an asynchronous RTU server, tcp-server for an asynchronous TCP server, and rtu-over-tcp-server for the asynchronous RTU over TCP server, which is the variant that lets a serial device be reached across a network. The two modes are a real architectural choice rather than a convenience. The async API is non-blocking and the default, while the sync API is blocking and optional, and the sync path exists so that code which is not built around an async runtime can still use the same protocol implementation.
Adding the dependency, and trimming the feature set
Adding the crate to a project is a Cargo.toml entry.
[dependencies]
tokio-modbus = "*"If you only need one part of it, the documentation shows how to narrow the features so the binary does not carry code you never call. For an asynchronous TCP client only, the entry disables the default features and asks for tcp.
[dependencies]
tokio-modbus = { version = "*", default-features = false, features = ["tcp"] }The same pattern works for the RTU client, the RTU server and the TCP server, each with the corresponding feature name. The reason this matters in practice is that the server implementations pull in extra machinery. The server features are where the payload framing and the connection handling live, and the dependency list in the manifest shows how the optional pieces are separated: futures-core and futures-util are optional, smallvec is optional, socket2 is optional, tokio-serial is optional and its comment says it is disabled by default features to exclude the unused dependency on libudev, and tokio-util comes in with the codec feature. So a client-only build avoids a serial port dependency it will never open.
Examples for every transport and mode
The examples directory is the fastest way to see the API, and the file names map cleanly onto the feature matrix. On the client side there are tcp-client.rs and tcp-client-sync.rs, the async and sync versions of the TCP client, and rtu-client.rs and rtu-client-sync.rs for the serial equivalent. There is also tcp-client-custom-fn.rs, which suggests the client accepts a custom function for some part of the flow, and tls-client.rs and tls-server.rs for the TLS variants, which is worth noting because TLS is not part of the Modbus protocol itself and is an addition on top of it. On the server side there are tcp-server.rs, rtu-server.rs and rtu-server-address.rs, and rtu-over-tcp-server.rs for the variant that bridges serial to network. The documentation points at the examples folder for various examples for Modbus RTU and TCP using either the asynchronous or synchronous API. The naming convention is worth internalising because the library's terminology follows it: the thing that polls is the client and the thing that answers is the server, the opposite of what the older Modbus words suggest.
Testing, and the scripts around the workspace
The workspace contains documentation, tests and examples for all available features, and the two commands to run them are short.
cargo test --workspace
cargo test --workspace --all-featuresThe first run covers the default feature set, and the second turns on everything, which is the one that matters for a library with this many optional combinations, because the failure mode of a feature matrix is a combination nobody tests. The repository has a set of shell scripts that show how the project is actually worked on: run_cargo-tests.sh, run_cargo-udeps.sh for finding unused dependencies, run_pre-commit.sh, and run_socat.sh, which is a hint that the test suite uses a serial tunnel to simulate a device. There is a pre-commit configuration, rustfmt.toml, a .taplo.toml for TOML formatting, a .commitlintrc.json, a rust-toolchain.toml pinning the toolchain, a .deny.toml for cargo-deny, and a .markdownlint-cli2.yaml. The docs.rs metadata asks for all-features = true, so the published API documentation covers every variant rather than only the default build. The last push to the repository was on 2026-07-22.
The protocol layer, and the dependency choices behind it
The library implements the Modbus specifications directly, and the README links the three documents it follows: the Modbus Application Protocol Specification v1.1b3, the Modbus over serial line specification and implementation guide v1.02, and the Modbus Messaging on TCP/IP Implementation Guide v1.0b. The dependency list is short enough to read as a design statement. byteorder handles the big and little endian conversions that Modbus requires, bytes and crc handle the framing and the checksum that the serial variants need, and thiserror provides the error types. socket2 and tokio-util with the codec feature are what let the crate sit on top of tokio's IO traits without owning the reactor, and futures-core and futures-util are optional because the sync path does not need them. The manifest also carries an unusual piece of care, a comment that says to specify precise dependency versions in Cargo.toml, with a link to a Rust users forum thread explaining why. The authors are credited as slowtec GmbH and Markus Kohlhase, and the copyright line runs from 2017 to 2026, which tells you how long the crate has been in service.
Where tokio-modbus is the wrong choice
The boundaries are worth stating plainly, because Modbus is a narrow protocol and the library implements that protocol exactly. First, this is Modbus only. If your device speaks Profinet, EtherNet/IP, OPC UA or a vendor specific dialect, none of it is here, and even within Modbus, vendors disagree about which function codes they implement, so the library can only give you the standard and you have to know the device. Second, the sync API is a second implementation surface rather than a thin wrapper, which means the two APIs can diverge in behaviour and you should be careful about how much you rely on the sync path. Third, there is no server-side security story beyond what the protocol offers, since Modbus has no authentication in its base specification, and the TLS examples are about protecting a TCP transport rather than about access control to the device. Fourth, and most practically, the async and sync features and the server features are all opt-in, so a Cargo.toml that has been copied between projects is the most likely cause of a confusing runtime error, and the feature matrix is the first thing to check. The last point is about Rust itself: the crate needs a current toolchain, edition 2024 and Rust 1.85 or newer, so it is not available to a project pinned to an older edition.
Editorial conclusion
Adopt tokio-modbus if you are writing Rust against Modbus devices and want the crate to fit an existing tokio service rather than sit beside it with its own thread pool. Do not adopt it if you need a Modbus implementation in a language your team does not use, since the whole library is Rust, and do not adopt it expecting TLS-secured or high-level industrial protocols beyond what Modbus itself offers. Verify two things before you build on it: that the cargo features you need are enabled, since the defaults cover only the async TCP and RTU clients and every server variant and the sync APIs are opt-in, and that the device you are talking to implements the function codes you need, since the library implements the protocol and not your vendor's dialect of it. The practical first step is to add the crate with the feature set you need and run the workspace tests, cargo test --workspace --all-features, to see the covered paths.
Frequently asked questions
What is tokio-modbus?
It is a pure Rust Modbus library based on tokio, offering client and server implementations over Modbus TCP and RTU, with an async non-blocking API as the default and a blocking sync API available as an optional feature. It is licensed MIT or Apache-2.0.
How do I add tokio-modbus to a Cargo project?
Add tokio-modbus to the dependencies section of Cargo.toml. To narrow the build, set default-features = false and list only the feature you need, for example features = ["tcp"] for an asynchronous TCP client only, or features = ["rtu-server"] for an RTU server.
Which cargo features does tokio-modbus provide?
The rtu and tcp features for the asynchronous RTU and TCP clients are on by default, while rtu-sync, tcp-sync, rtu-server, tcp-server and rtu-over-tcp-server are optional and must be enabled explicitly.
Why does tokio-modbus call the master a client and the slave a server?
The README says Modbus is based on a master/slave communication pattern, and that to avoid confusion with the tokio terminology, the master is called the client and the slave is called the server in this library.
How do I run the tokio-modbus tests?
The workspace contains tests and examples for all available features, and the documentation gives cargo test --workspace for the default feature set and cargo test --workspace --all-features to cover everything.
Official sources
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.
[](https://hysenlabs.com/projects/slowtec-tokio-modbus)Was this useful?