Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I split up a large impl over multiple files?

Tags:

rust

I really don't like monolithic implementations of the functions for a class (in C++ speak). In that language I can split things up as I like; in Rust there are strict rules about what goes in what files.

I have about 2000 lines (no comments/ docs) of impl for a struct. Logically they can be broken up into different sets; functions for managing aspect A, functions for managing aspect B, ... They all noodle on the struct's data big time, so chopping the struct up further wont help.

I saw in one answer that you can have

// in thing.rs

struct Thing{
.......
}

impl Thing{
  fn1
  fn2
}
// in more_thing.rs

use crate::thing::*;
impl Thing{
  fn3,
  fn4
}
// in lib.rs

mod thing;
mod more_thing;

This works, almost (I was surprised it worked at all). Its a kind of half way house. The problem is that for the methods in more_thing.rs I have to declare the fields of Thing all pub. Which is doable but not great. Are there any other options?

I know I can limit the pub scope but still it blows encapsulation.

like image 820
pm100 Avatar asked Aug 12 '20 04:08

pm100


3 Answers

All non-pub items in a module are still visible in its submodules. Just make more_thing a submodule of thing instead of a sibling. You can do this by putting it in a directory named thing, and putting the mod declaration inside thing.rs:

// thing.rs (or thing/mod.rs; see below)
pub struct Thing {
    field: i32,
}

// Note the lack of `pub`: `more` is only an implementation detail
mod more;
// thing/more.rs
use super::Thing;

impl Thing {
    // Although it is defined in a non-`pub` module, this method will be visible anywhere
    // `Thing` is because it is marked `pub` and is a member of `Thing`. You can use
    // `pub(crate)` or `pub(super)` instead to get different levels of visibility, or
    // leave it private and it will only be available in the current module (thing::more)
    pub fn field(&self) -> i32 {
        // because more is a submodule of thing, non-`pub` members are visible here.
        self.field
    }
}

If you wish to keep all the Thing-related files in the thing directory, you can rename thing.rs to the special filename thing/mod.rs and it will work in exactly the same way.

like image 69
trent Avatar answered Oct 10 '22 05:10

trent


All methods are private by default in Rust, which means they are only accessible in the scope of their containing module (which is never larger than a file), in this case thing.rs. thing.rs is just as remote to more_thing.rs as any libraries and external code; it may as well be a separate crate. In essence, you are trying to declare a private method on an external item, which of course fails.

However, this can be a bit confusing because when it comes to orphan rules, you can always implement traits for any item in the same crate, not just the same module. This is because trait implementations are always public (as long as you can access both the trait and the item implementing it; the actual orphan rules are a bit more complicated but this is the basic idea).

In essence: trait implementations are public, but method implementations are private by default. In order to implement public things you need at least public access, and in order to implement private things you need private access.

Instead, one solution is to simply declare a function that takes your item as an argument. For example, in more_thing.rs:

use super::thing::Thing;

fn foo(thing: &Thing) {
    // ...
}
like image 21
Coder-256 Avatar answered Oct 10 '22 06:10

Coder-256


Why not put impl blocks in the same file?

in thing.rs

struct Thing {
.......
}

impl Thing {
  fn1
  fn2
}

impl Thing {
  fn3
  fn4
}

The methods are splited though there is still a large file.

like image 31
snylonue Avatar answered Oct 10 '22 06:10

snylonue