How can I separate the implementation of the Default trait for a structure in another file?

Viewed 221

I have about a hundred fields in the structure, so I want to store the struct definition and the data in separate files.

Specifically, I want to keep the definition in a file, and the default implementation in another file. I want to access the default implementation from main.rs. I am not sure how to achieve it.

Directory tree

.
├── Cargo.toml
├── src
│   ├── config.rs
│   ├── lib
│   │   └── config_types.rs
│   └── main.rs

config_types.rs

#[derive(Debug, Copy, Clone)]
pub struct Config {
    a: u8,
    b: u8,
}

impl Config {
    pub fn new(a: u8, b: u8) -> Self {
        Config { a, b }
    }
}

config.rs

#[path = "./lib/config_types.rs"] mod config_types;

use config_types::Config;

impl Default for Config {
    fn default() -> Self {
        Config {
            a: 0b0000_0001,
            b: 0b0000_0000,
        }
    }
}

main.rs

#[path = "./lib/config_types.rs"] mod config_types;
#[path = "./config.rs"] mod config;

use config_types::Config;

fn main() -> ! {
    let config = Config::default();
}

The error I am getting:

error[E0599]: no function or associated item named `default` found for struct `config_types::Config` in the current scope
  --> src/main.rs:64:26
   |
64 |     let config = Config::default();
   |                          ^^^^^^^ function or associated item not found in `config_types::Config`
   | 
  ::: src/./lib/config_types.rs:2:1
   |
2  | pub struct Config {
   | ----------------- function or associated item `default` not found for this
   |
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `default`, perhaps you need to implement it:
           candidate #1: `Default`

If I move the default implementation into config_types.rs, the error goes away.

How can I achieve this?

2 Answers
  1. Remove the #[path] attributes. Instead, use regular modules and re-export as needed.
  2. Make the fields public in the entire crate.

src/main.rs

mod lib {
    pub mod config_types; // Point 1
}
mod config;

use lib::config_types;  // Point 1
use config_types::Config;

fn main() {
    let config = Config::default();
}

src/config.rs

use crate::config_types::Config;  // Point 1

impl Default for Config {
    fn default() -> Self {
        Config {
            a: 0b0000_0001,
            b: 0b0000_0000,
        }
    }
}

src/lib/config_types.rs

#[derive(Debug, Copy, Clone)]
pub struct Config {
    pub (crate) a: u8,  // Point 2
    pub (crate) b: u8,  // Point 2
}

impl Config {
    pub fn new(a: u8, b: u8) -> Self {
        Config { a, b }
    }
}

I have about a hundred fields in the structure

That makes it sound like this is an autogenerated file. See the code generation example for build scripts for other ideas on how to solve this. If it's not an autogenerated file, I don't see how separate files make anything easier.

I'd avoid creating a module called lib because that tends to be too confusing with the common lib.rs file.

If I was going to do this myself, I'd flip it so that the generated file is included into the hand-edited file:

src/main.rs

mod config;

use config::Config;

fn main() {
    let config = Config::default();
}

src/config.rs

include!("autogenerated.rs");

impl Config {
    pub fn new(a: u8, b: u8) -> Self {
        Config { a, b }
    }
}

impl Default for Config {
    fn default() -> Self {
        Config {
            a: 0b0000_0001,
            b: 0b0000_0000,
        }
    }
}

src/autogenerated.rs

This file probably wouldn't exist in the src directory if it were generated!

#[derive(Debug, Copy, Clone)]
pub struct Config {
    pub a: u8,
    pub b: u8,
}

Note that these no longer need to be public.

The line mod config_types; declares a new module. Even though you point them to the same file via #[path = ...], they will result in different declarations of Config:

  • crate::config_types::Config
  • crate::config::config_types::Config

You then try to use Default for one when it is actually defined on the other.

So only use one or the other:

  • remove mod config_types; from config.rs and import it via main.rs instead via use crate::config_types;
  • remove mod config_types; from main.rs and export it from config.rs via pub use config_types::Config;

Using #[path = ...] is very rarely appropriate. For example, the one for config is totally unnecessary and will work without it. I recommend going over the Managing Growing Projects with Packages, Crates, and Modules chapter of the Rust Book, which does a good job of explaining modules and how to import/export items properly.

Related