Programming

Re-Exporting and Privacy in Rust

In this blog post, we’ll explore the concepts of re-exporting and privacy in Rust, which play crucial roles in controlling how items are accessed and exposed across modules.

 

Reexporting allows developers to reorganize and simplify module hierarchies, making external application programming interfaces (APIs) cleaner and more intuitive using the pub use keyword. Additionally, we’ll delve into the privacy rules for structs, explaining how fields can be selectively made public or kept private to encapsulate data effectively.

 

Re-Exporting with a Pub Use Expression

Let’s demonstrate how the library we’ve created can be used in the main function. To keep things simple, we’ll use the basic version of the library without organizing it into separate files. We’ll proceed by creating instances of the Product and Customer structs within main. To do this, we’ll need to bring the Product and Customer structs into scope via a use declaration, as follows:

 

use my_package::{customer::Customer, product::Product}; // Error

fn main() {}

 

The compiler is giving us errors indicating that the modules are private. This can be fixed by making these modules public in the lib.rs file, as indicated in this listing.

 

pub mod product {

   ...

}

   pub mod customer {

...

}

mod order {

   ...

}

 

The errors have now disappeared. However, this approach is not efficient. In main, we only need to use the Customer and Product structs, not their entire modules. Additionally, we shouldn’t expose and make everything public unnecessarily. You can bring specific items from inside a module into the scope of code, outside the crate, without making the entire module public. We can achieve this selective visibility by using the pub keyword alongside the use statement, as shown in this listing.

 

pub use customer::Customer;

pub use product::Product;

mod product { // not needed to be pub anymore

   ...

}

mod customer { // not needed to be pub anymore

   ...

}

mod order {

   ...

}

 

The pub keyword before the use declaration enables you to re-export an item from our top-level module, without needing to make the module public. Only the specific item will be made visible to the outside world. Note that we do not need to make the customer and product modules public anymore.

 

In main, instead of mentioning the full path, we can now use the reduced paths, as in the following examples:

 

use my_package::{Customer, Product};

 

We intend to create an instance of the Product in main. Since the Product struct needs the Category enum, let’s also re-export it from library, as shown in this listing.

 

pub use customer::Customer;

pub use product::{category::Category,Product}; // Error

mod product {

   ...

}

mod customer {

   ...

}

mod order {

   ...

}

 

The compiler throws an error that the module category is private. The module category is not at the top level, but with re-exporting, we can make it available from the top-level modules. To properly re-export, we’ll first re-export the Category enum from the product module to the crate module by using the pub keyword, as shown in this listing.

 

pub use customer::Customer;

pub use product::{category::Category,Product};

mod product {

   pub use category::Category;

   ...

}

mod customer {

   ...

}

mod order {

   ...

}

 

Error: Latest Rust Versions

This syntax might throw an error in the newer versions of Rust (1.80 or later). In the latest versions, you must indicate the re-export of public items from a submodule by mentioning the crate module, as shown in this listing.

 

pub use customer::Customer;

pub use product::Product;

pub use crate::product::Category; // this syntax will work on newer versions

mod product {

   pub use category::Category;

   ...

}

...

 

In summary, the pub use declaration is quite handy when you want to create a public interface for an item defined in a different module, and you want external code to access that item without needing to navigate the entire module hierarchy.

 

Privacy of Structs

Let’s create an instance of the Product struct in the main function, as shown in this listing.

 

use my_package::{Category, Customer, Product};

fn main() {

   let product = Product {

      id: 1, // Error

      name: String::from("Laptop"), // Error

      price: 799.99, // Error

      category: Category::Electronics, // Error

   };

}

 

We get a bunch of errors stating that the fields are private. Let’s inspect the Product struct in lib.rs, as shown in this listing.

 

pub use crate::product::Category;

pub use customer::Customer;

pub use product::Product;

 

mod product {

   pub use category::Category;

   pub struct Product {

      id: u64,

      name: String,

      price: f64,

      category: category::Category,

   }

...

}

...

 

The Product struct is public. In Rust, making a struct public does not make its fields public. You have two options to fix this error, which we’ll discuss next.

Making All the Fields Public

The first approach is to make all the fields public, as shown in this listing.

 

pub use crate::product::Category;

pub use customer::Customer;

pub use product::Product;

 

mod product {

   pub use category::Category;

   pub struct Product {

      pub id: u64,

      pub name: String,

      pub price: f64,

      pub category: category::Category,

   }

...

}

...

 

However, this solution is not always ideal. You may want to keep certain fields private. For example, the id field might be for internal use and record-keeping, so we shouldn’t allow external code to modify it.

Adding a New Constructor Function

Thus, we’ve come to our second approach, where we’ll keep some fields private while still providing controlled access. To achieve this set up, create a new associated function called new (or create a constructor function) inside the implementation block. You can use this new constructor to create a new instance of the struct. Consider the example shown in this listing.

 

pub use crate::product::Category;

pub use customer::Customer;

pub use product::Product;

mod product {

   ...

   impl Product {

      pub fn new(id: u64, name: String, price: f64, category: Category) ->

Self {

         Self {

            id,

            name,

            price,

            category,

            }

         }

         ...

   }

}

...

 

The inputs to the function are the field values for the struct, and the output is an instance of the Product. Inside the function, the field values are initialized from the values passed in. Since the variable names passed in are the same as the names of the struct fields, we can simply use the variable names.

 

Privacy in Enums versus Structs

The privacy rules for enums are slightly different from the rules that govern structs. When an enum is made public, all of its variants automatically become public as well. Unlike structs, you cannot set the individual variants of an enum to be public independently.

 

Let’s add a similar constructor (new) function for creating a new Customer in the lib.rs file. Consider the example shown in this listing.

 

...

mod customer {

   ...

   impl Customer {

      fn new(id: u64, name: String, email: String) -> Self {

         Self { id, name, email }

      }

   }

}

...

 

In main, you can create instances of the Product and Customer using these new constructor functions, as shown in this listing.

 

use my_package_book::{Category, Customer, Product};

fn main() {

   let product = Product::new(1, String::from("Laptop"), 799.99,

Category::Electronics);

 

   let customer = Customer::new(1, String::from("Alice"),

 

String::from("alice@example.com"));

}

 

In the same way, you can use the Order struct in main by first re-exporting it, then making the Order struct public, and finally adding a new constructor function for creating a new order.

 

Editor’s note: This post has been adapted from a section of the book Rust: The Practical Guide by Nouman Azam. Dr. Azam is an associate professor of computer science at the National University of Computer and Emerging Sciences. He also teaches online programming courses about Rust and MATLAB, and reaches a community of more than 50,000 students. 

 

This post was originally published 7/2025.

Recommendation

Rust: The Practical Guide
Rust: The Practical Guide

Get “close to the machine” by programming with Rust! Discover how to effectively use this low-level language to create fast and efficient code. Set up Rust, compile your first program, and learn the language essentials: variables, functions, conditionals, and more. Walk through Rust’s unique ownership model and modular system, and then move on to more complex features, from flexibility and abstraction to web programming and text processing. Numerous code examples and exercises make this your go-to practical resource for Rust!

Learn More
Rheinwerk Computing
by Rheinwerk Computing

Rheinwerk Computing is an imprint of Rheinwerk Publishing and publishes books by leading experts in the fields of programming, administration, security, analytics, and more.

Comments