We often start small coding projects, and we often tend to start them off in a single file. But sometimes, the project naturally grows larger and more complex. The code thus becomes more difficult to manage in a single file.
At these times we often need to break the code up across multiple files so that we can focus on a particular functionality. This is where I found myself as I am toying with my SPF code.
The rust guide on this is pretty clear. While I was collecting some links for this article. I also came across this short youtube post.
Current Code Structure
My current src structure is shown below.
@trust-dns#tree src
src
└── main.rs
With about 460
lines of code. Probably past time to re-organise the code.
Since this is merely a project for learning. I am not being very picky with my naming convention. So I opted to create a folder simply called dns
. This is where I will move the basic portions of code.
DNS Folder
The directory structure now looks like:
src
├── dns
│ ├── mechanismkind.rs
│ ├── mod.rs
│ ├── mx.rs
│ ├── soa.rs
│ └── spf_mechanism.rs
└── main.rs
dns/mechanismkind.rs
Contains
#[derive(Debug, Clone)]
pub enum MechanismKind {
//snip
}
impl MechanismKind {
/// Returns `true` if the mechanism_kind is [`Include`].
pub fn is_include(&self) -> bool {
matches!(self, Self::Include)
}
//snip
}
dns/mx.rs
Contains the code related to an MX lookup response
use trust_dns_resolver::config::*;
use trust_dns_resolver::error::ResolveResult;
use trust_dns_resolver::lookup::MxLookup;
use trust_dns_resolver::Resolver;
pub fn display_mx(mx_response: &ResolveResult<MxLookup>) {
match mx_response {
Err(_) => println!("No Records"),
Ok(mx_response) => {
//snip
}
dns/soa.rs
Contains the code related to a SOA response
use trust_dns_resolver::error::ResolveResult;
use trust_dns_resolver::lookup::SoaLookup;
pub fn display_soa(soa_response: &ResolveResult<SoaLookup>) {
//snip
}
fn convert_rname_to_email_address(rname: &String) -> String {
let rname = rname.clone();
//snip
}
dns/spf_mechanism.rs
Contains the code related to a TXT response, specifically SPF
use crate::dns::mechanismkind::MechanismKind;
use ipnetwork::IpNetwork;
#[derive(Debug, Clone)]
pub struct SpfMechanism<T> {
kind: MechanismKind,
qualifier: char,
mechanism: T,
}
impl SpfMechanism<String> {
pub fn new_include(qualifier: char, mechanism: String) -> Self {
SpfMechanism::new(MechanismKind::Include, qualifier, mechanism)
}
//snip
}
impl SpfMechanism<IpNetwork> {
pub fn new_ip4(qualifier: char, mechanism: IpNetwork) -> Self {
SpfMechanism::new(MechanismKind::IpV4, qualifier, mechanism)
}
//snip
}
impl<T> SpfMechanism<T> {
//snip
}
There is also this extra file named mod.rs
. This file has three purposes.
- It is an index of the files/modules contained within the directory
dns
- Rust maps the directory name and the file
mod.rs
so they are seen as one and the same. So in this casedns
is more or less an alias tomod.rs
. - Because of point 2. It also can also contain code for the given module. Hopefully this will become clear.
Lets take a look at mod.rs
pub mod mechanismkind;
pub mod mx;
pub mod soa;
pub mod spf_mechanism;
pub mod mechanismkind
tells rust there is a file called mechanismkind.rs
inside dns
. This applies to the other lines within mod.rs
.
In this case there is no code. I am using dns
as a root for my module.
But because mod.rs
contains these lines, I can now make use of use
to include the code they contain. These are also marked pub
so they can be used in code written in main.rs
Example:
use crate::dns::mechanismkind::MechanismKind;
This boils down to:
Look inside dns
-> mod.rs -> look inside mechanismkind
-> mechanismkind.rs and from mechanismkind.rs use MechanismKind
and the code which defines its enum
and impl
main.rs
At the beginning of main.rs
I now have:
mod dns; // Rust now knows about the directory called `dns`
use crate::dns::spf_mechanism::SpfMechanism; // Access and use the code in `spf_mechanism.rs`
Also main.rs
now only contains 264
lines of code.
If you want to see the code at this stage you can access it here
If you want to the next iteration where I made the structure a little nicer
src
├── dns
│ ├── mod.rs
│ ├── mx.rs
│ ├── soa.rs
│ └── spf
│ ├── kinds.rs
│ ├── mechanism.rs
│ └── mod.rs
└── main.rs
And resulted in main.rs
only containing 64
lines of code. You can visit here
In this case spf/mod.rs
contains the code for Spf
, and loads the code from kind.rs
and mechanism.rs
pub mod kinds;
pub mod mechanism;
use crate::dns::spf::mechanism::SpfMechanism;
use ipnetwork::IpNetwork;
#[derive(Default, Debug)]
pub struct Spf {
source: String,
//snip
}
Because this code lives inside mod.rs
I have listed kinds
and mechanism
so that rust knows these files exist within the spf
directory.
Spf
depends onSpfMechanism
which is found inmechanism.rs
SpfMechanism
depends onMechanismKind
which is found inkind.rs
Again, these are just some of my rambling thought as I learn a bit more about rust.
If you have thoughts, advice. I would be glad to hear them.