Coming from Python, how do we call other Rust files for code?

Hello, I come from a Python heavy background, and utilizing multiple "scripts", to reuse code in different programs. In Rust I understand use std::env; etc., but what about scripts, other programs we have written?

For example, in Python from local.code import this_function, lets assume that "this_function" is a Rust script. Do I just use use local::code::this_function; ?

Note I don't come from a computer-science or development background, so I do apologize if my terminology is busted.

If by "other scripts/programs" you mean splitting the code for a single program into multiple files, you use mod to tell rust to compile another file.

In src/main.rs

mod other_mod;
use other_mod::other_fn;

In src/other_mod.rs

pub fn other_fn() {
    println!("printed from other module");
}

If you mean sharing code between programs, there are quite a few different options, with different tradeoffs. But in general you split code you want to share into a new crate, and reference that crate from your projects. Exactly how you do that will vary depending on exactly what you're trying to do

The rust book has a section on splitting code into crates and modules which might be useful to reference

https://doc.rust-lang.org/stable/book/ch07-00-managing-growing-projects-with-packages-crates-and-modules.html

3 Likes

Yes; you use use to import your own symbols in almost exactly the same way as you'd use it to import foreign symbols or stdlib symbols.

However, you'll also need to set up a module hierarchy, so that Rust can find your symbols. The Book has a cheat sheet right at the start of the section on modules generally.

1 Like

So what would be the etiquette here. Lets say we are building a new program from scratch. Not importing code from other local sources. Is putting all the code into one Rust file, frowned upon? (Let's imagine the program is quite large.) I know most "main" Python files were generally just a 'dunder' statement to run the program, minus all the importing as well.

Personally I usually try to keep my files under ~500 lines[1] to help keep my bearings on what code is where. It really comes down to personal stylistic choices though.

Having one huge file can make merge conflicts much worse, though if you don't have a whole team working on the code that's probably not a concern.


  1. not including any unit tests ↩︎

2 Likes

When I start a new project, I'll normally have everything in a single lib.rs file while I'm doing my inital hacking.

Then, once some common concepts/abstractions start to appear (e.g. after 1-2 hours/300 lines), I'll start splitting things into sub-modules. This makes it easier to find things[1] and avoid the problem where I can no longer fit all the concepts and functions in my head for a 2000-line file, whereas that's easy to do when the file is only 100 lines and I can absorb it all with a 5 second skim.

Later on as the project gets larger (e.g. after a week/1500 lines), you'll notice that you've grown different sub-systems or interfaces and want to spin them off into separate crates so they can be used independently each with their own responsibilities. To use the example of a compiler, I might make one crate for the parser, one crate for the type checker, one crate for code generation, and a separate crate with the command-line program that ties it all together.

To continue with @semicoleon's comment about merge conflicts, I find that collaborating with a team is a lot easier when you've got multiple crates because it's easy to say "crate A is mostly developer X's responsiblity and developer Y looks after crate B". That way it's easy to avoid stepping on each other's toes by having different developers mostly interact via their public APIs (see Conway's Law).

This can help when working alone, too. It means I could make one PR which updates one crate and also work on a second PR that updates another crate, then when it comes to merging the two different PRs the process should be relatively painless because I've been keeping things separate and self-contained.


  1. For example, if you are working on a compiler and want to see the definition for an AST type, it's a lot easier to check the 300-line src/syntax/ast.rs than searching through a 2000-line lib.rs file for that one struct definition. ↩︎

4 Likes

Understood, thank you!

Hello again Michael, I have been experimenting with this to make sure I understand it. But I am a little confused by what I have seen so far. I have two files like this:

  • src
    | _ main.rs
    |_ routing.rs

In routing.rs, I have a function (pub fn main_routing_post), that I am bringing in to main.rs using use unnamed::main_routing_post. This errors out with use of undeclared crate or module 'unnamed'. But when I change the name of the file routing.rs to lib.rs. It compiles, why is that?

Cargo has certain conventions that it handles automatically. If there's a lib.rs file cargo assumes that defines a new library crate. If you have a file named main.rs cargo assumes that defines an executable which uses the crate defined by lib.rs. So with lib.rs cargo automatically connects the parts together, but it isn't actually doing what you intended (including the file directly in your executable) though both work.

You can also fix the error by adding mod routing; above the use which tells the compiler that there's a module named routing defined in a file (by default it assumes the file is called routing.rs)

I was reading up on that, and I had tried keeping everything as is. Two files (routing.rs, main.rs), main.rs mod routing; use unnamed::main_routing_post; But still errors out with use of undeclared crate or module unnamed? error[E0432]: unresolved import unamed

Oops sorry, I missed that part. The use should be

use routing::main_routing_post;

It's like a relative filesystem path, you don't need to use the name of the crate (I assume your crate is called unnamed?)

1 Like

Lol that worked, I swear I tried so many different variants I got all switched around. Thank you for your help with that.

So in the end, what exactly is the purpose of the lib.rs file, if you can name the files whatever you want when importing them? Just out of curiosity.

lib.rs is the library crate root. Right now you're just creating an executable, which is fine for starters. But if you wanted to publish a crate that other people could import into their own programs, that was also used in your executable, you'd need to define a library crate so other programs could link against it.

One of the other reasons to put most of your code in the library crate rather than in your executable directly, is that you can't use cargo's integration tests[1] on code that's only in an executable.


  1. tests in the tests folder next to src ↩︎

2 Likes

Hey Semi, so I am still having issues with importing other items from different files. I don't know what is happening. But when I add a folder into the "src" directory, and attempt to import it. It states its not found.

--src
|__modules
------ |__main_routing
|__main

Yesterday I got it working, but when I moved it to a new folder in the src directory and appended to "modules::" to the front of both the "mod" and "use", it started stating that it could not find it again. I feel like I am doing exactly what the documentation is showing in "Defining modules and control scope and privacy". But I guess I am not?

When you have a module that's a folder as opposed to a module that exists in a single file, you need a "representative" file for that folder. There are two ways to do this, one is to create a file next to the folder with the same name. This is the "new" way.

The old way is to create a file called mod.rs inside the folder. [1]

The "representative file" will contain all of the mod whatever; statements for the other rust files in the folder.

So if you create a file next to main.rs called modules.rs and put mod main_routing; in that file, you should now be compiling all of those files.

Assuming main_routing contains a function called routing_func:

If you want to access all of the items in main_routing directly from modules you can do

mod main_routing;
// You can also use * instead of routing_func to re-export ALL items in the module
pub use main_routing::routing_func;

And then access the items in main_routing with modules::routing_func

If you want main_routing to be its own namespace you can instead do

pub mod main_routing;

And then access the items in main_routing with modules::main_routing::routing_func.

In general if you want to add a module named one you:

  1. Create a file named one.rs inside the parent module. This will be the "representative file" for the module, which defines what other files will be imported, if any
  2. Add mod one; to the parent module's "representative file". In your case the parent module is defined by main.rs so main is the "representative file". You may need to come back and change the visibility of the module, or reexport items from it, but this is all you need to do to get the compiler to compile the new module.
  3. If the module is going to contain other files create a folder named one in the same folder as one.rs, and repeat these steps for any of the modules you want to add inside one. (Though the "parent module" will now be one instead of main in your example)

  1. personally I still find the old way less confusing, especially given how many editors tend to group folders together rather than listing them in alphabetical order with files. But that's neither here nor there ↩︎

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.