Netbox to Netshot: how I used Rust for the first time

I joined Scaleway in January 2021 to build the new Network Systems & Automation team in charge of all the tools and infrastructures to help the Network Engineers team. At this point, I had been coding mostly in Python and Go, and I was looking for a useful, non-critical project to start experimenting with Rust and evaluate if it would be interesting for our internal stack development.

Scaleway uses a lot of different internal systems for our daily work and to maintain all the systems working properly.

One of those we have been using for a long time is the well-known Netbox, as our DCIM/IPAM, to manage all our hardware inventory and IP addressing plan. We recently deployed another less-known open-source product called Netshot for our network configuration compliance and backup needs, but also to replace our old RANCID, which was only doing configuration backups and was harder to maintain.

Writing an open-source program to solve this problem

As Netbox is our source of truth for the inventory and we have an unfathomable amount of network devices - switches, routers, and all that - to manage, it would be out of the question to add all the existing and new devices by hand on Netshot every time we have something added in Netbox.

We searched for an existing tool that could synchronize devices from Netbox to Netshot, but could not find anything.

I had a lot of experience with Python and Go, but I wanted to experiment with Rust for a new, non-critical project and this project was a good candidate.

Rust vs. Python vs. Go

I was looking to work with Rust because it appeared to be a language that was focused on safety and good performance. I had played around a little bit with it, but this was my first full project, and I was up for the challenge.

I like working with static type safety, and IDEs can help with this in a few languages. But as Python's IDE are not always optimal with type safety, refactoring and completion features (it gives a kind of static typing with type hints - but it's not enforced, not always available as Python is dynamic, not always predictable and it doesn't provide any real safety in the end). So, I chose Rust.

I was also looking for a language that could be compiled or packed in a single runtime-less binary so we would not have to manage any Python or library version requirements.

I like advanced features like generics, optional types, pattern matching, and proper error management, so Rust seemed more appropriate than Go.

The project would have run the same on Python... But with the hassle of having to manage the dependencies everywhere we want to deploy it, or having to create heavy container images just for it - It is slower to deploy and harder to maintain.

It would have run the same on Go too, but there the error management wouldn't have been so effective: missing generic types in Go can also sometimes make you do dirty workarounds and Rust give you some interesting functional approaches (pattern matching, powerful collection library).

Coding was more effective in Rust

The project would have worked the same in Python or Go, but the coding was more effective in Rust. The experience was good for the first project in a new language. The error messages when the compilation is failing are very explicit, and usually include the solution of the problems directly in it. The libraries usually have good documentation, like reqwest, serde, and structopt in my case, which makes them easy to use, and the autocompletion features of the various IDE help compared to what you can get from dynamic languages like Python (type hints are not as precise a real static typing)

The fact that the tests are directly included in each Rust file as a submodule is a bit surprising when you come from other languages. But it’s easy to manage, and the integrated test system from cargo is user-friendly.

I had some trouble with memory management because when you are used to coding with languages using garbage collectors like Python or Go, you are used to the compiler giving some hints but not always pointing at the origin of the problem. As it wasn’t a persistent problem for me, I used .clone() from time to time to avoid increasing the complexity too much.

I also liked the fact that there are Cargo plugins that would allow for example to directly create deb and rpm archives to distribute the applications easily on various Linux distributions.

The community has been very helpful, I usually find the answers quite easily when googling, asking colleagues that have more experience than me on Rust, or asking on the Rust Discord chat.

Post-Mortem

In the end, it was a good experience, I enjoyed using Rust and I will use it again for my next projects. Probably some code running directly on Cisco Nexus devices to avoid using old Python 2. Rust will become one of the default languages in the team as we keep being pleased by its features. Rust where we can, Python where we must.

Having developed this tool saved us a lot of time and hassle by not having to add all the new devices manually. The project is available here, next to other open-source projects Scaleway contributes to as well. So, if you have the same problem: help yourself.

Recommended articles