Managing big Rust impls by splitting them up

Have you ever been in the situation, that you had to write a big module or class, and the implementation took more and more lines and you ended up with one file, which was several thousand lines long? Finding things in there is tedious and takes up a lot of time. Debugging is a nightmare as you jump lots of lines all the time when you go out of scope, so you have to scroll a few lines around in order to understand where you are.

I have had the mentioned kind of situation in Node.JS earlier and now again in Rust. So I want to actively fight it! For Node.JS, I came up with a unique new way to organize my modules. It is a lot of work, but once everything is in place and you need to work some more on a module, debug it, or just understand part of it, you will really come to love it.

Since I really like the way I manage my Node.JS modules, I decided to, instead of coming up with a completely new way, adapt it to Rust modules. My initial approach was to google how I can split up struct impls, but unfortunately, the search did not yield any usable results. So I started to play around and came up with something quite close to what I did in Node.JS. The split-up code in a Cargo project looks like this:

// /src/main.rs

// Three lines for import is still something I have to sort out...
mod foo;
use foo::_struct::Foo;
use foo::*;

fn main() {
    let f = Foo{ my_var: true, };
    f.say_hello();
}
// /src/foo/mod.rs

// Also not too pretty; so much boilerplate for a little source code distribution across files
pub mod _struct;
pub mod say_hello;
pub use self::say_hello::SayHello;
// /src/foo/_struct.rs

/**
 * Struct with all the import fields goes here!
 * I will probably have to add all the methods as comments here to have one single interface...
 */
pub struct Foo {
    pub my_var: bool,

    /**
     * Write a friendly "Hello" to the console
     *
     * fn say_hello(&self);
     */
}
// /src/foo/say_hello.rs

use super::_struct::Foo;

pub trait SayHello {
    fn say_hello(&self);
}

impl SayHello for Foo {
    fn say_hello(&self) {
        println!("Hellow World <3");
    }
}

Unfortunately, the Rust node is not very active, yet, so I was not able to receive feedback. Being a bit unhappy with a few parts, I tried going directly to the Rust community and hoped for feedback there. One awesome users (troplin) answered my thread soon after and opened my eyes! impls can be split and there can be several impls for one struct in different files! As a fact, I tried to split them some time ago, but the compiler did not let me do so. Also it seems that solution is not well known at all (shock)! No wonder I could not find anything about it on Google! But it is such a simple and awesome solution and might become very important, that it itched me to write this story about it and make it a little bit more well known.

Now, for the most exciting part, the actual new pattern I will use to manage my code:

// /src/main.rs

mod foo;
use foo::Foo;

fn main() {
    let f = Foo{ my_var: true, };
    f.say_hello();
}
// /src/foo/mod.rs

mod say_hello;

pub struct Foo {
    pub my_var: bool,
}
// /src/foo/say_hello.rs

use foo::Foo;

impl Foo {
    pub fn say_hello(&self) {
        println!("Hellow World <3");
    }
}

It can easily scale by adding the functions as mod imports to /src/foo/mod.rs and just adding all the impls into new files in the module directory, like /src/foo/say_hello.rs. A very awesome and tidy solution!

Write your comment…

Be the first one to comment