berkes
berkes

Reputation: 27603

How to use a trait in multiple modules

While trying to DRY and reorganize some code in a project, I'm running into issues with scoping and modules.

src/main.rs:

mod account;
use account::Account;

mod vacancy;
use vacancy::Vacancy;

fn main() {
   let key = String::from("s3cr3t");
   let uri = String::from("https://localhost:7700");

   let account = Account { name: String::from("jdoe") };
   account.into_meili(&uri, &key);

   let vacancy = Vacancy { title: String::from("CEO") };
   vacancy.into_meili(&uri, &key);
}

src/account.rs:

pub struct Account {
    name: String
}
impl IntoMeili for Account {
    fn into_meili(&self, uri: &String, key: &String) {
        println!("storing document in {} using {}", uri, key);
    }
}

src/vacancy.rs:

pub struct Vacancy {
    title: String
}
impl IntoMeili for Vacancy {
    fn into_meili(&self, uri: &String, key: &String) {
        println!("storing document in {} using {}", uri, key);
    }
}

src/into_meili.rs:

pub trait IntoMeili {
    fn into_meili(&self, uri: &String, key: &String);
}

I'm at a loss how to tie these together. I may have gotten some concept entirely wrong, though, so maybe the structure is mistaken in the first place.

When I bring into_meili into scope in the two modules, I need to tell about the path attribute, which hints that my structure is un-rust-full to begin with:

src/vacancy.rs:

#[path="into_meili.rs"]
mod into_meili;
use into_meili::IntoMeili;
// ... same as above.

Same in src/account.rs. This gets rid of the obvious not found in this scope errors for IntoMeili.

This returns the errors to src/main.rs, in the form of method not found in Vacancy for account.into_meili(). The suggestion is to bring the IntoMeili into scope. With a weird suggestion: use crate::account::into_meili::IntoMeili;. Hinting that, again, my structure is wrong, as I want to bring crate::into_meili::IntoMeili in scope instead?

Both suggestions don't work. If I bring use crate::account::into_meili::IntoMeili; and use crate::vacancy::into_meili::IntoMeili; it errors with private module for into_meili.

When I use mod into_meili; use into_meili::IntoMeili; I still get method not found in Vacancy, because, apparently, this is the wrong trait according to this structure.

I probably don't understand a concept in Rust correct; there's a lot to learn in Rust. Obviously the examples are simplified greatly, and because I'm fighting the compiler, above code won't compile. I've added a compiling version in the footnotes, which simply keeps modules out of it entirely.

So, how would I structure my project?


Compiling example on rust-playground:

pub trait IntoMeili {
    fn into_meili(&self, uri: &String, key: &String);
}

pub struct Account {
    name: String
}
impl IntoMeili for Account {
    fn into_meili(&self, uri: &String, key: &String) {
        println!("storing document in {} using {}", uri, key);
    }
}

pub struct Vacancy {
    title: String
}
impl IntoMeili for Vacancy {
    fn into_meili(&self, uri: &String, key: &String) {
        println!("storing document in {} using {}", uri, key);
    }
}

fn main() {
   let key = String::from("s3cr3t");
   let uri = String::from("https://localhost:7700");

   let account = Account { name: String::from("jdoe") };
   account.into_meili(&uri, &key);

   let vacancy = Vacancy { title: String::from("CEO") };
   vacancy.into_meili(&uri, &key);
}

Upvotes: 0

Views: 918

Answers (1)

kmdreko
kmdreko

Reputation: 60662

Am I structuring the code and modules wrong?

Its mostly that you've not yet understood how files and modules link together. You should never have to use the #[path = ...] attribute. Your main.rs should declare that the other files exist via mod:

mod account; // you already
mod vacancy; // have these

mod into_meili; // add this

Then to access the into_meili module to implement the trait for Account and Vacancy, you can import it via:

use crate::into_meili::IntoMeili;
// or
use super::into_meili::IntoMeili;

See: How do I import from a sibling module?

So your working example is more akin to this:

// into_meili.rs
mod into_meili {
    pub trait IntoMeili {
        fn into_meili(&self, uri: &String, key: &String);
    }
}

// account.rs
mod account {
    use super::into_meili::IntoMeili;

    pub struct Account {
        name: String
    }
    impl IntoMeili for Account {
        fn into_meili(&self, uri: &String, key: &String) {
            println!("storing document in {} using {}", uri, key);
        }
    }
}

// vacancy.rs
mod vacancy {
    use super::into_meili::IntoMeili;

    pub struct Vacancy {
        title: String
    }
    impl IntoMeili for Vacancy {
        fn into_meili(&self, uri: &String, key: &String) {
            println!("storing document in {} using {}", uri, key);
        }
    }
}

// main.rs
use account::Account;
use vacancy::Vacancy;
use into_meili::IntoMeili;

fn main() {
   let key = String::from("s3cr3t");
   let uri = String::from("https://localhost:7700");

   let account = Account { name: String::from("jdoe") };
   account.into_meili(&uri, &key);

   let vacancy = Vacancy { title: String::from("CEO") };
   vacancy.into_meili(&uri, &key);
}

For more explanation I think the blogs Clear explanation of Rust’s module system and Rust modules vs files do a good job of explaining things.


Other than that, your code looks fine to me.

Is it "rust-ish" at all to define methods like fn into_meili on objects in the first place, rather than having e.g. a meili::vacancy_into_meili() etc?

Both have their uses, but you'd use traits if both Account and Vacancy are intended to be treated similarly/generically.

Upvotes: 3

Related Questions