DEV Community

Adam.S
Adam.S

Posted on • Edited on • Originally published at bas-man.dev

Manage Rust Code by Breaking it Up

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

There is also this extra file named mod.rs. This file has three purposes.

  1. It is an index of the files/modules contained within the directory dns
  2. Rust maps the directory name and the file mod.rs so they are seen as one and the same. So in this case dns is more or less an alias to mod.rs.
  3. 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;
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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`
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 on SpfMechanism which is found in mechanism.rs
  • SpfMechanism depends on MechanismKind which is found in kind.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.

Top comments (0)