This is a different kind of post from what you’re used to read on this blog.
Today I won’t be sharing a new release or achievement, but rather a more technical behind-the-scenes about the ongoing development of the project.
We’ll have a look at the framework that powers Sniffnet’s user interface,
and at how the Rust programming language can be a life-saver when it comes to restructuring a codebase.
But first, let me take a step back and explain why I decided to write this post in the first place.
The problem
While developing a new feature for Sniffnet, I realized that some important architectural changes were needed to make it work as intended.
The feature in question is the ability to import offline data from a PCAP file in addition to live network adapters monitoring.
One of my goals was to abstract the data source, so that most of the code could be used to handle both live and offline data.
This required a major rework of the way Sniffnet handles and displays network traffic,
and made me realize that the old infrastructure was not flexible enough to accommodate these changes.
In particular, the frontend was tightly coupled with the backend routine in charge of parsing Internet traffic,
making it difficult to separate the two concerns.
The side effects on the UI caused by capturing packets from a file instead of a network interface were more than I originally expected:
one of the most annoying ones was that while processing live data we can simply periodically poll the backend for traffic intensity,
this is not anymore true when importing from a file given that the read speed isn’t known apriori and data in the file can potentially
contain time gaps.
I had two options in front of me:
- duplicate some of the UI logic and adapt it to support the respective data source
- rethink the frontend architecture to make it more flexible and decoupled from the backend
The first option? A quick and easy fix — wait that’s awesome!
The second one? A more time-consuming and by far more complex solution — blah,
who wants to rethink core parts of a 20k+ lines codebase that works?
Who the hell would choose the second route?
Well, if this topic is worth a blog post, it’s obvious I chose option 2.
It’s pretty clear that option 1 would’ve made the codebase way more difficult to maintain in the long run.
The Elm Architecture
As you may already know, Sniffnet’s UI is built on top of the iced library.
The framework takes inspiration from the Elm Architecture,
a programming pattern to build interactive applications.
The Elm Architecture is based on four main components:
- model — the application’s state
- view — a way to graphically represent the state
- messages — commands that trigger updates to the state
- update — a way to update the state in reaction to messages

This separation of concerns allows for a deterministic flow of data,
enabling to reconstruct the state of the application at any point in time given the initial model and the list of messages that have been processed by the update logic.
In iced, what empowers these ideas even further are the constraints imposed by the Rust programming language.
In fact, Rust’s concepts of ownership and immutability make it possible to enforce a single source of truth for the application’s state,
and to ensure that the state is only modified in a controlled manner.
In other words, this means the state cannot be modified directly by the view logic or by any other part of the codebase,
but only via producing messages that are then processed by the update logic (the only component that has access to a mutable reference to the state).
So again, what’s the problem?
I mentioned earlier that the UI was tightly coupled with the backend routine parsing network traffic,
but this seems to be in contrast with Elm principles of separating concerns and having a single source of truth.
So, how is that possible?
Some parameters of Sniffnet’s state were wrapped in structures
(see Arc
and Mutex
)
that allow having shared, interior-mutable access to them.
When I first started developing Sniffnet almost three years ago,
I was eager to use this pattern to let secondary threads access and modify the state of the app directly.
While this is a recurrent pattern in Rust and is generally a blessing in achieving fearless concurrency, it’s not ideal using it for the iced application’s state.
At the time, it was generally less clear to me how having a single source of truth could help keeping the flow of data smoother.
A fact that was confusing to me was also that iced’s view logic had mutable access to the state
(not how it’s supposed to be, and later fixed).
The solution
With time, I started realizing that using a different approach would’ve brought several benefits,
but it’s only when I started working on PCAP file import that I finally decided to take the plunge and rethink
how the backend interacts with the UI.
Instead of letting the backend modify the state remotely,
I started using the full power of iced’s message handling system
(see Task
and Subscription
)
to asynchronously send messages from the secondary threads to the frontend update logic.
There were so many moving pieces that I was reluctant to do this at first.
In the end, despite the consistent amount of needed changes, Rust’s powerful type system allowed me to
define proper message kinds to correctly handle all the different scenarios,
and the compiler, as always, was my best friend in orchestrating the whole process.
I won’t go and bore you with the details but if you’d like to, feel free to check all the implementation specifics in the relative pull request on GitHub.
Conclusion
Even though the architectural changes object of this post don’t impact the user experience or the app’s performance, it’s a relief to know that everything is now less prone to bugs and easier to maintain.
What I can say is that I already loved iced for its concepts,
despite I wasn’t using them to their full potential.
This, hence, is also the story of how I fell in love with iced once again.
Wait, one last thing.
Can I be honest with you?
I don’t care about flexing Sniffnet is written in Rust.
I don’t care about flexing my Rust skills either.
I don’t care if “Rust” sounds cool.
But I do care about not getting crazy when major changes are needed.
And I do care about having a clean and maintainable codebase that makes me sleep at night.
Well, Rust makes this possible.
It’s not marketing.
It’s not a trend.
It’s not a hype.
It’s not only about performance and safety.
It’s also, and most importantly, about developer experience.
This, in my humble opinion, is the real power of Rust.