Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

It's dangerous to go alone, `pub` `mod` `use` this.rs

Tags: code rust path

❶ Author of How to Open Source (.dev). A book to take you from coder to contributor. ❷ Creator of CodeTriage, a free service helping developers contribute to open source. ❸ Core committer to ruby/ruby. ❹ Married to Ruby, literally. © Richard Schneeman What exactly does use and mod do in Rust? And how exactly do I “require” that other file I just made? This article is what I wish I could have given a younger me.This post starts with a fast IDE-centric tutorial requiring little prior knowledge. Good if you have a Rust project and want to figure out how to split up files. Afterward, I’ll dig into details so you can understand how to reason about file loading and modules in Rust.Tip: If you enjoy this article, check out my book How to Open Source to help you transform from a coder to an open source contributor.Skip this if: You’re the type of person who likes to see all the ingredients before you see the cake.For this tutorial, I’ll be using VS Code and the rust analyzer extension.With that software installed, create a new Rust project via the cargo new command:Note: The exa command isn’t required. I’m using it to show file hierarchy. You can install it on Mac via brew install exa.In the src directory, there’s only one file, main.rs. Since some people think tutorial apps are a joke, let’s make an app that tells jokes.Create a new file named joke.rs by running:Add a function to the file:Then modify your src/main.rs file to use this code:This code fails with an error:You cannot use the code in src/joke.rs code as src/main.rs cannot find it.When you create a file in a Rust project and get an error that it cannot be found, navigate to the src/joke.rs file in your VS Code editor and hit CMD+. (command key and period on Mac, or control period on Windows). You’ll get a “quick fix” prompt asking if you want:Your editor may look different than mine, but note the quick-fix menu right under my cursor:If you hit enter, then your src/main.rs should now look like this:Skip if: You already know how to use cargo-watch in the VS Code terminalRun your tests on save in the vscode terminal with cargo watch. You can open a terminal by pressing CMD+SHIFT+P (command shift “p” on Mac or control shift p on Windows). Then type in “toggle terminal” and hit enter. This will bring up the terminal. Then, install cargo watchNow run the watch command in your terminal:This command tells cargo to watch the file system for changes on disk, then clear (-c) the window and execute tests (-x test). This will make iteration faster:Make sure cargo watch is running and save the src/main.rs file. Note that tests are failing since the program still cannot compile:The error “exists but is inaccessible” is similar to what we saw before but with additional information. If you run that rustc command, it suggests:The very last line gives us a great clue. We need to update the function to be public. Edit your src/joke.rs file to make the function public:On save, it still fails, but we’ve got a different message (always a good thing):It suggests “consider importing this function” by adding use crate::joke::want_to_hear_a_joke; to src/main.rs. Use the quick-fix menu CMD+. on the function in src/main.rs:After accepting that option, src/main.rs now looks like this:When I save the file, it compiles!To recap what happened here:You don’t have to memorize EVERYTHING required. All in all, our tools either did the work or gave us a strong hint as to what to do next. Start mapping if-this-error to then-that-fix behavior while learning use, mod, and file loading.To import code from a file in the src/ directory into src/main.rs, you must add a mod declaration to src/main.rs.How do you add files in a different directory?Let’s say we want to tell several kinds of jokes, so we split them into a directory and different files. Use the results of the first tutorial and add to it:Now add some code that we can import. Write a joke into src/joke/knock_knock.rs:And another into src/joke/word_play.rs:With these files saved, use the CMD+. quick-fix menu, which will prompt you to insert a mod. Select the first option on both files and save both.You might be surprised it didn’t modify src/main.rs like our first tutorial. Instead, the extension modified the file src/joke.rs with these additions:Notice:Modify src/joke.rs to use the contents of those modules now:These two lines implicitly use the module imported above to its own namespace. You can also make this explicit using the self keyword:You can save and run this code. Make sure you’re sitting down so you don’t end up rolling on the floor laughing.To recap what happened here:The want_to_hear_a_joke function will output two jokes. Sometimes my kids think a joke is so funny that they want to hear it again. Let’s add cow_date again. This time put it directly in the main() {} function:When you save, you’ll get an error:We’ve seen this error before “cannot find function in this scope”. The help ends with this line:“If the item you are importing is not defined in some super-module of the current module, then it must also be declared as public (e.g., pub fn).”That’s not super helpful since the cow_date function is already public:Before we saw that these two invocations are basically the same thing:So you might guess that calling cow_date() inside of src/main.rs is the same as calling self::cow_date(), which makes it a bit more explicit. You might also notice that cow_date is nowhere in src/main.rs. Where did it come from?That function came from src/joke/word_play.rs, but Rust cannot find it. The first time we called the function, we started with self and traversed the path. Let’s try that same technique:Did that work?No. But, our message changed again (always worth celebrating). Before, the error said that cow_date was “inaccessible.” Now it’s saying that word_play is a private module.It points at a line in src/joke.rs, where word_play is defined for self::joke::word_play.Hover over word_play in src/main.rs and press CMD+.. It asks if you want to change the visibility of the module:Accept that change and save. Then the file compiles!To recap what happened here:You might wonder, “What’s pub(crate) and how is it different from pub?The pub(crate) declaration sets visibility to public but is limited to the crate’s scope. This is less privileged than pub. The pub declaration allows a different crate with access to your code (via FFI or a library) to use that code. By setting pub(crate), you indicate a semi-private state. Changing that code might affect code in other files of your project, but it won’t break other people’s code.I prefer using pub(crate) by default and only elevating to pub as needed. However, the core part of this exercise was seeing how far we could get by letting our tools figure out the problem for us.If all you want to do is put code in a file and load it from another file, you’re good to go. This is the high-level cheatsheet for what we did above:Reference Rust code in a file fast by:If you use the above methodology, that rust analyzer will create files. There are two ways to load files from a directory. Before, I used the convention that src/joke.rs loaded all the files in the src/joke directory. That’s technically the preferred way of loading files in a directory, but there’s one other method which is: putting a mod.rs file in the directory you want to expose.In short, src/joke/mod.rs would do the same thing as src/joke.rs. Please don’t take my word for it. Try it out:Here’s the current file structure:Now move the joke file to src/joke/mod.rs:Now here’s what our directory looks likeIf you re-run tests. They still pass! As far as Rust is concerned, this code is identical to what we had before.Now that you’ve tasted our lovely IDE productivity cake. It’s time to learn about each of the ingredients. Here’s what we’ll cover:In the above example, we saw that we can reference code via a path starting with self like self::joke::word_play::cow_date. Here’s what you can start a code path within Rust:Starting a path with self indicates that the code path is relative to the current module. Before we saw this code:In this case, self is inside the src/joke.rs module. This keyword is optional in this case. You can remove it, and the code will behave exactly the same. Most published libraries do not use ‘ self ‘ because it’s less typing to omit the word altogether. Most would write that code like this:In this code, self is implied because we’re not using crate or super.Using self can be helpful as a reminder that you’re using a relative path when you’re starting. If you’re struggling to correct a path, try mentally substituting the module’s name (in this case, joke) for the self keyword as a litmus test to see if it still makes sense.While it might seem that self and unqualified are interchangeable, they are not. This code will compile without self:However, this code will not:With error:In addition to being an implicit reference to self, using an unqualified path also allows you access to elements that ship with Rust, like the println! macro or std namespace:If you put self:: in front of std::fs above, it would fail to compile.Using an unqualified path also gives you access to any crates you import via Cargo.toml. For example, the regex crate:If you put self:: in front of regex above, it would fail to compile.A code path that starts with crate is like an absolute file path. This keyword maps to the crate root (src/main.rs or src/lib.rs).In our above example, self was also crate since we were in the src/main.rs, so we could have written this code like this:As this:Because self refers to src/main.rs, these two lines of code are identical. However, if you try to copy and paste them to another file, only the one that starts from crate will continue to work.You can use an absolute path in any code module as long as all the parts of that path are visible to the current module. For example, here’s the src/joke.rs file using a mix of absolute and relative paths:This code calls cow_date via its absolute cargo path and tank_knocks from a relative path.The super path references the path of a parent. In this case, the super of src/joke.rs is src/ (otherwise known as the crate root). You can re-write the above code using super then as a replacement for crate like this:Skip this if: You know how to use use to rename modules.In Rust, there is a filesystem module std::fs, but you don’t have to use it to well…use it. You can type out the full path:Instead of writing out std::path::PathBuf and std::fs everywhere, you could tell Rust that you want to map a shorthand and rename it:This code says, “Anytime you see PathBuf in this file, know what I mean is std::path::PathBuf”. This pattern is so common you don’t need the repetition of as at the end. Since you’re use-ing it as the same name, you can write this code:All three programs are exactly the same. The use does not do anything. It simply renames things, usually for convenience.Beyond importing a namespace or a single item (such as an enum, struct, or function), you can import ALL the items in a namespace using a glob import.For example:In this code, we can call the read_to_string function without naming it above. That’s because read_to_string exists at std::fs::read_to_string, so when we import std::fs::*, it gets imported along with all of its std::fs::* friends.This is generally discouraged because two imports may conflict. If one-day std::path introduces a new function named std::path::read_to_string, then there would be a conflict, and your code would fail to compile.While glob imports might save time typing, they increase the mental load while reading. They’re not currently considered “idiomatic.” The exception would be for a prelude file within a crate.Beyond renaming imports, use can change your code’s behavior!In Rust, you can define a trait on a struct that wasn’t defined in the same library through something known as an “extension trait”. This might sound exotic and weird, but it’s common. Even Rust core code uses this feature.For example, this code will not compile:You’ll get this compile error:But if you add a trait via use, now this code will compile:Why did that work? In this case, the use statement on the first line changes the code by bringing the FromStr trait into scope. Also, Rust was clever enough to realize that it could compile if we added a std::str::FromStr onto the code. It’s right there in the suggestion. Neat!This behavior change confused me for the longest time as most use documentation and tutorials just beat the “Rust does not have imports, only renaming” drum to the point that it’s not helpful information. Confusingly enough, if you define the FromStr trait on your own struct, you’ll have to also use std::str::FromStr everywhere you want to use that behavior too.The various permutations of pub, mod, and use can be confusing, to say the least. I wrote this out as an exercise in understanding them. It’s more useful as a reference:I’ve focused on mapping modules and files, but you can use modules without files. Rust by example modules gives some examples. You’ll likely find modules used without filenames as you go through various docs on use or the module system. Aside from mod.rs and /.rs, most features map 1:1 whether or not your modules are backed by a file system.I really wanted to call this a “comprehensive” guide, but there’s more to this rabbit hole. If you want a depth-first kind of learner, you can dive into some further reading:Join thousands of developers who get new code, writing, and programming links from me delivered to their inboxes. The other day I got another question about the zombocom org on GitHub that prompted me to write this post. This org, github.com/zombocom, holds most all of my popular libraries). Why put them in a custom GitHub org, and why name it zombocom? Let’s find out.Read MoreI came to love pairing after I hurt my hands and couldn’t type. I had to finish up the last 2 months of a graduate CS course without the ability to use a keyboard. I had never paired before but enlisted several other developers to type for me. After I got the hang of the workflow, I was surprised that even when coding in a language my pair had never written in (C or C++), they could spot bugs and problems as we went. Toward the end, I finished the assignments faster when I wasn’t touching the keyboard, than I was by myself. Talking aloud forced me to refine my thoughts before typing anything. It might be intimidating to try pairing for the first time, but as Ben puts “it’s just a way of working together.”Read MoreToday is the day. How to Open Source is now available for purchase at howtoopensource.dev.Read MoreToday I’m going to share my perspective on how Ruby on Rails is developed and governed and how I feel the Basecamp “incident” impacts the future of Rails. I’m going to start out telling you what I know for sure, dip into some unknowns, and dive into some hypotheticals for fun.Read MoreTravisCI.org is dead. Long live the new CI! TravisCI.org was THE way to run CI for an open source Ruby library. It was so easy that it was seemingly effortless. Even better, it was free. Since the slow-motion collapse of the product, developers have been pushed to other CI providers. I was recently tasked with transferring CI away from Travis for my library derailed_benchmarks and chose CircleCI. This post is a little about why I chose CircleCI, a little about how the transition worked, and a little about nostalgia.Read More



This post first appeared on VedVyas Articles, please read the originial post: here

Share the post

It's dangerous to go alone, `pub` `mod` `use` this.rs

×

Subscribe to Vedvyas Articles

Get updates delivered right to your inbox!

Thank you for your subscription

×