Project layout confusion - how to use shared code in two binaries

Hello,

trying to understand how to properly layout the code...
I've read about modules, uses, namespaces etc., but still fail to understand how to successfully compile following scenario:

crate structure:

C:.
│   Cargo.toml
└───src
    │   utilities.rs      // shared code i want to use inside of all sub-binaries 
    |                     // (could also be in form of folder/mod.rs...)
    └───bin               // as instructed by cargo book etc.
            howto-cmd.rs  // want to use fns from utilities.rs here
            howto-gui.rs  // and here as well

I understand that i need to tell how to reach "utilities.rs" in the .rs file where i want to use it.

However, i fail to realize how to do it...

Using mod/uses commands seem to lead to various error messages, closest one being:

  • recommendation to "mod" the .rs file that is sibling of the howto-cmd/gui.rs,

but:

  • i want to "include" (coming from C world) the .rs module from one level up...
    (crate:: prefix seems not to help either)

utilities.rs:

pub fn util_hello() {
    println!("util hello");
}

howto-cmd.rs:

// what do i need here?
// uses::... ???
// mod ...   ???
fn main() {
    println!("hello from CMD app");
    utilities::util_hello();
}

thank you, b.r.,
j.

You have multiple options here:

  1. #[path = "../utilities.rs"]
    mod utilities;
    

    And then use utilities::item to refer to some item defined (pub) inside utilities.rs.

    • More generally, crate::utilities::item if within a submodule inside howto-....
  2. include!("../utilities.rs");
    

    This is the less Rust-idiomatic solution, but may be the closest one to fit your C mental model.

    I do not recommend using it in the long term, since you lose namespacing, as well as any privacy features from utilities.rs itself, but may be a good starting point.

    Once you are comfortable using include!("../utilities.rs");, you can follow up with namespacing:

    mod utilities {
        include!("../utilities.rs");
    }
    
    • and have all the items in utilities.rs be marked pub.

    This will be the same as the initial basic include!, but now you need that utilities:: namespace prefix to refer to the items of that file :slightly_smiling_face:

    At that point, you can toy with removing some pub-annotations inside utilities.rs, if you so wish, and see how the "namespace" is also a privacy boundary: that's what a module is.

    And once you have figured that out, just know that

    [path = "../utilities.rs"]
    mod utilities;
    

    is the idiomatic short-hand equivalent of:

    mod utilities { include!("../utilities.rs"); }
    
    • In this case, not that much of a shorthand since you had to provide an explicit path annotation due to the circumstances, but in general,

      mod some_name;
      

      will be equivalent to:

      • either mod modname { include!("modname.rs"); }

      • or mod modname { include!("modname/mod.rs"); }

      (chosen by Rust depending on which file is actually present).

  3. The third option involves Cargo, and the layout of dual bin/lib packages (i.e., a package (a "project" of sorts)) that features both a library crate and one or more binaries (binary crates) that depend on that own lib as if it were an external one.

    That is, if your Cargo.toml is of the form:

    [package]
    name = "some_name"
    ...
    

    Then starting at lib.rs (but you can override that default "root path"), a library crate named some_name will be defined, and you will be able to refer to its items from within your howto-... crates as with the item of any other external crate (within the 2018 edition, which has been the default one for a while now, so you can ignore / dismiss this remark): ::external_crate_name::item, that is, here, ::some_name::item (from the [package] name, remember).

    It also happens that the Rustaceans are too lazy to type a leading ::, so you can even skip writing it (leading to some_name::item), although, if you are starting, I recommend you don't do that until you have clearly understood how the Rust item paths work and how they are resolved.

    This means that in your case, you could edit your Cargo.toml and add:

    [lib]
    name = "utilities"
    path = "src/utilities"
    

    which will override both the default root path for that library (instead of the default src/lib.rs), and the name used to refer to its items (::utilities instead of ::some_name)

    This means that from within src/bin/*.rs you will be able to refer to the items defined in utilities.rs by using ::utilities::item, without needing to add anything to those src/bin/*.rs files whatsoever :ok_hand:

1 Like

thank you for extensive and clear explanation and the variants!

i stumbled upon #2 in some blogposts/discussions, and found it a bit fishy - rather C-like looking, thus my previous attempts to try various :: paths in mod/use statements...

1 Like