avatarBruce H. Cottman, Ph.D.

Summary

The provided content outlines the author's experience and recommendations for setting up a Rust development environment, including the use of tools familiar from Python development, the installation and benefits of Rust and Cargo, configuring Jupyter for Rust, commenting practices, IDE choices, and testing in Rust.

Abstract

The author shares their journey of adapting to Rust from Python, emphasizing the importance of a well-configured development environment. They detail the installation process of Rust and its package manager, Cargo, and how they've adapted Jupyter, a Python-centric tool, to work with Rust for interactive computing. The author also discusses the importance of commenting in Rust to maintain code readability and the integration of various IDEs, such as PyCharm with a Rust plugin, to enhance the development workflow. Additionally, the author touches on the use of Cargo for dependency management, versioning, and testing, advocating for its role in achieving literate programming. The article concludes with reflections on the Rust ecosystem's future and the resources available for Rust developers.

Opinions

  • The author values the performance and safety benefits of Rust while acknowledging the learning curve and the need for extensive commenting to maintain code readability.
  • There is a preference for using Cargo over rustc due to its comprehensive project management capabilities, which the author likens to a Continuous Development (CD) tool.
  • The author suggests that Rust's approach to dependency management and compatibility is superior to Python's, due to Cargo's robust resolution algorithm.
  • Jupyter is seen as a valuable tool for Rust development, despite some limitations with dependent crates, which the author has found ways to work around.
  • The author expresses a positive opinion about the Rust plugin for JetBrains IntelliJ IDEA, highlighting its seamless integration and features such as code highlighting, debugging, and testing.
  • The author is open to exploring new Rust testing tools and frameworks, emphasizing the importance of thorough testing practices.
  • There is an acknowledgment of the discomfort with Rust's lack of object-oriented programming features in its core, which is a departure from languages like Python and C++.
  • The author encourages the Rust community to continue developing tools and practices, mentioning Polars as an example of a Rust implementation that addresses issues found in Python's Pandas library.

Optimizing My Rust Development Environment

We were commanded by the powers that be for our team to be the "guinea pig" that migrates to Rust. My approach entailed configuring various tools I liked as a Python developer for my Rust Development Environment (RDE). I was surprised when the other team members incorporated many of my RDE approaches. I am sharing the details with you.

Rust Logo, Cartoon rendering by https://www.befunky.com

It would be a very dull world if all used the same name for the same concept.

But when you think about it, the terms do not mean the same concepts for solid reasons: each term is different.

The dependencies in Rust and Python diverge due to the fundamental dissimilarities in their language paradigms and packaging formats.

Rust is a statically-typed compiled language that distributes packages, known as crates, in precompiled binaries. Conversely, Python is a dynamically-typed interpreted language that distributes its packages as source code.

The difference in distribution methods impacts the installation and management of dependencies in each language.

Rust's package manager, Cargo, is integrated with the compiler and enables hassle-free dependency management. Conversely, Python has several package managers, with Pip being the most popular.

Cargo's dependency resolution algorithm guarantees compatibility between all packages, facilitating the management of dependencies and minimizing version conflicts. Python's dependency resolution is more relaxed, sometimes resulting in version conflicts.

These discrepancies between dependencies in Rust and Python ultimately stem from each language's unique priorities and design goals.

Rust emphasizes the performance of a strongly typed compiled language, threading for modern multi-core computers, and safety for C. Python emphasizes ease of use and readability.

Installing Rust and Cargo

The installation that my teammates and I use is from https://doc.rust-lang.org/book/ch01-01-installation.html. The Rust community supports it, and it is quick, less than 1 minute, on my MacPro 2013 with the old Intell Xeon E5 chipset.

curl https://sh.rustup.rs -sSf | sh
The tail of output on my terminal from the above install of Rust.` curl

Note: shmight not work. use /bin/bash

Jupyter is not an Integrated Development Environment (IDE)

Jupyter is an interactive computing and data exploration environment, quite popular with the Python and Machine Learning community. The emphasis for me is Interactive Computing Environment (ICS), also known as a Read-Eval-Print Loop or REPL editor.

I started my career in computing on an IBM 360 using punch cards and getting in one compile every 12 hours; if my day started at 2:00 AM.

I use Jupyter for Python.

Something else will best Jupyter, but using Python and Jupyter; I have evolved from compiles per hour to how many functions I can write per hour with five unit tests.

No lie. I am approximately 25 times more productive with Python/Jupyter than with C/Emacs.

Jupyter possesses certain features commonplace in Integrated Development Environments (IDEs), including code editing and debugging.

The fundamental objective of Jupyter is to enable interactive computing, data analysis, and exploratory programming, which renders it an indispensable tool for tasks such as coding and debugging, as well as data cleaning, transformation, and visualization.

I use both an ICS and an IDE. I use an ICS, Jupyter, to mature a bunch of related functions, and for an IDE, I use Pycharm with Rust plugin to roll out to Test. I describe in more detail the Rust plugin later in this article.

How to configure Jupyter as an ICS for Rust

I want Jupyter to experiment with Rust code.

ExCxR for Jupyter is an open-source project (free!) that enables Rust code to be executed in a Jupyter notebook.

To use Rust in Jupyter, you must install and put the kernel in your Jupyter environment.

I show you step-by-step:

Note: I assume you are familiar with Python. If not, I recommend using the Rust documentation.

  1. You probably have Jupyter already installed. If you do not, install Jupyter via the Python loader pip:
pip install jupyter

2. Next, download and build the evcxr crate by running the following command in your terminal:

cargo install evcxr_jupyter
The tail of output on my terminal from the above install of evxcr_jupyter. This installation takes about 10

3. Next, install the evcxr_jupyter kernel by running the following command in your terminal:

evcxr_jupyter --install
The tail of output on my terminal from the above build of evxcr_jupyter. This installation takes about 1 second of clock time on my computer.

4. You can start a Jupyter notebook server using the jupyter notebook command:

jupyter notebook

5. Create a new Rust notebook: In the Jupyter notebook interface, click on "New" -> "Rust" to create a new Rust notebook.

6. Copy and paste the code from the previous answer into a cell in the notebook.

7. Run the code by pressing Shift+Enter or clicking on the "Run" button in the notebook interface. Here's an example of how the Rust "Hello, world" code and output appear in a Jupyter notebook:

A Hello-World function compiled and executed in Jupyter! That is so cool!

Here's an example of a recursive version of the Fibonacci function in Rust to calculate the nth Fibonacci number:

Note: Break evcxr_jupyter with fibnacci(100).

Note: You may need to adjust the Jupyter notebook settings to display the output of the Rust program, which may not be displayed by default. You can do this by selecting “Cell” -> “All Output” -> “Toggle Scrolling” from the menu bar.

Comments

A great feature of Python is its readability by humans.

With Rust, if you wrote the code, it is readable that day. Next month, not so much.

If you release your Rust code to Test without comments or harmful comments, your code will be one of the last to pass Test. How unfair!

Note: We test but we call the quality control group — Test.

Rust has an official style guide for comments, found in the Rust documentation.

At least we think we understand what the Rust community accomplished with their comment style guide. It can also be used as a style guide for C.

However, IMHO, Rust needs a lot of commenting to be readable.

Here is what we did for our style guide. We merged the "Google Rust Style Guide" and the "Google Python Style Guide" and came up with two Rust comment style rules:

  1. Use doc comments to document crates, functions, methods, structs, enums, traits, and modules. A doc comment is a comment that starts with /// or /**. The documentation provides a high-level overview of what the item does, how it works, and any important details relevant to users of the item.

For example:

/// functiion sum: Returns the sum of two numbers.
/// # Examples
///
/// ```
/// let result = sum(2, 3);
/// assert_eq!(result, 5);
/// ```
fn sum(a: i32, b: i32) -> i32 {
    a + b
}

Arguments are undocumented in this example as they self-document the name, argument name, and type. If the arguments used are non-obvious, then they should be documented.

2. Do not use inline comments. If you feel you must use /* ... */ ON THE LINE ABOVE to provide implementation details irrelevant to code users.

Use inline comments sparingly and only when necessary. Inline comments should provide additional context or explain tricky code, but the code should be self-explanatory whenever possible.

For example:

fn main() {
    let x = 5; // BAD INLINE COMMENT Initialize x to 5
// GOOD, BUT NOT REALLY. SORT OF OBEYS STYLE RULE
// Add 2 to x to get y
    let y = x + 2;
    println!("The value of x is {} and the value of y is {}", x, y);
}

We are using comments in our Rust code, so it is well-documented and readable for you and others who may follow.

Remember, we are (were) heavily influenced by Python, and are beginners at Rust. Our Rust comment practices may not be suitable for you.

There will probably be further evolution in our Rust comment (doc) practices.

Rust Integrated Development Environments( IDEs)

All the information was gathered from developer blog sites, programmer gossip, and sometimes product documentation.

The features of the IDE we use now, JetBrains PyCharm ( or IntelliJ IDEA) with Rust Plugin, deserves one or more blog articles. No promises, but you may see them in the future.

There are more than eight IDEs that I know of. I’ve briefly discussed four IDEs (Integrated Development Environments) that support multi-languages, including Rust in this external blog.

Visual Studio for Rust

If you are on a Windows platform, I have heard from other colleagues that Visual Studio is a great IDE for Rust. It might be great for MacOS and Linux. However, we stopped our evaluation at the Visual Studio for Rust documentation. It was not present for the macOS. We may not have looked hard enough. There was excellent documentation for Microsoft Windows.

Imagine our surprise when we concluded that Microsoft needed to support Apple's macOS or Linux fully.

Note: OK, we were not that surprised.

JetBrains IntelliJ IDEA Rust plugin

We choose the JetBrains plugin for Rust. Installing the Rust plugin into PyCharm was effortless.

Also, it had a tremendous but useless feature. If we launched PyCharm before loading, the Rust plugin download page would change.

Rust plugin download page changes if you load PyCharm.

Install the Rust plugin, and then you may have to restart PyCharm.

Plugins are shown with Pycharm|Preferences| Plugins

The documentation for IntelL (PyCharm) plugin is quite good, IMHO.

The PyCharm plugin for Rust features:

  1. Code highlighting and completion;
  2. Cargo integration: The plugin integrates with Cargo, Rust's package manager (project manager) ;
  3. Seamless integration with GitHub;
  4. Debugging: The plugin includes a built-in debugger;
  5. Testing: The plugin includes support for running Rust tests and provides a test runner tool window to display test results.
  6. Documentation: The plugin provides quick access to Rust documentation within the IDE.

If there is enough interest, as shown in the comments, I will write a blog article on the various features we use in the IntelliJ IDEA plugin for Rust.

Eclipse

We used Eclipse for a small Scala project. It was ok. We found it took up a large memory footprint, took a long time to load, and was awkward to use. Admittedly, we used it two years ago and were already using PyCharm.

If you are unfamiliar with a Rust IDE, Eclipse is a good choice.

Emacs

If you have used Emacs for 30 years or more, you should stay with Emacs. If you invested heavily in Lisp (Scheme) macros, you want to stay with Emacs.

The great news is that Emacs is a highly customizable (Lisp) text editor with good Rust support in Rust mode. The bad news is you will be lonely.

Versioning, Dependencies, Compatibility

I am not sure if I should tell you, but you can compile and run using the command rustc.

Nobody, literally, nobody I know uses rustc,everybody I know uses cargo.

If you installed the rust environment, following the install procedure I laid out above, then typing at your terminal should result in something like the following:

If it does not display a version, try the install again.

Most of the rest of this blog article is going to be confusing until you get cargo working.

Cargo

Most Rustaceans use this tool (cargo) to manage their Rust projects because Cargo handles a lot of tasks for you… rust documentation.

Cargo benefits from all the package managers that came before it. Pip is a great example of a less-than-optimal package manager.

  1. Cargo is a part of Rust. Cargo is (usually) installed with Rust;
  2. cargo new <project-name> creates the directory <project-name>, puts a minimal sub-directory structure, initializes it as a local git repository, and creates the important cargo.toml.

Alternatively, you can join an existing project that has an existing Rust git server repository. For example, the repo for this blog article is https://github.com/bcottman/rustDevEnv.

# cd <to directory to place local copy of repo>
$ git clone https://github.com/bcottman/rustDevEnv.git
$ cd rustDevEnv
$ ls -alx

The directory structure and files after a git clone for the rustDevEnv project are:

We found it best, as we usually have more than one developer working on a project, to have one developer :

  1. Create the rust project using cargo new <project-name>;
  2. Then create a repo of the project on a git server.

All other developers can now clone off the git server repo.

Note: If you had a git version for over a year, you should upgrade. The git version I am using:

3. cargo build completes initializing the local project as a rust project with src/main.rs(code for hello world), compiles, downloads dependencies (crates) specified in cargo.toml file, and links to produce the executable. The local project directory is now:

Great. cargo build is pointing out that I should use rest_dev_env , not restDevEnv.

4. cargo checkcompiles but does not produce the executable. It is faster and therefore you can get more compiles per millisecond.

5. cargo build --release to compile it with optimizations and places the executable in ../target/release.

6. cargo run

7. cargo testsearches for the#[test] attribute in your Rust code to run them as tests.

8. rustdoc src/<filename> -crate-name <name>

Summary of the some cargo's project management commands

Note: I consider cargo to be a Continuous Development (CD) Project Manager. CD is part of the CD/CI that defines DevOps.

Literate Programming

About two years ago, I discussed a twenty-year-old idea by Donald Knuth called "literate programming."

The key idea is that code, and associated tests and documentation for that code are located in one source file. Of course, you could have many different source files in your project (crate).

For me, cargo achieves "literate programming" for Rust.

The other significant benefit of cargo is that it manages dependencies and ensures compatibility between different versions of Rust crates. Also, cargo is tightly integrated with Git and fetches dependencies from Git repositories.

Jupyter needs to work with a rust project.

Earlier, I discussed the wonders of Juptyer's compatibility with Rust. I implied that you could put any rust code in Jupyter and evaluate it.

You can do this so long as the Rust code does not have dependent crates. For example, the following Rust code cut&paste into a Jupyter notebook fails:

extern crate num_cpus;

fn main() {
    let num_cores = num_cpus::get();
    println!("Number of cores: {}", num_cores);
}

main()

However, it will work if you used to set up a Rust project withcargo build and put num_cpus in crate.tomlas a crate dependency.

It is worth repeating, Jupyter must be run in a project directory with all dependent crates specified in the file crate.toml.

Note: It may be obvious to you, but it was not to us, that you need to put each crate and the version number of that crate in the [dependecy] section of crate.toml. Dependectcy listing in crate.toml is not generated from source code. It could be. Let me if there is a tool that does this.

Note: The crates available, about 50,000, and version numbers are found at https://crates.io/. FYI, pyhon has about 500,000 packages at https://pypi.org/.

Testing

cargo test is a command-line tool that is included with Rust's Cargo build system. It is used to run the tests in a Rust project.

Add the #[test] attribute to mark your code as test code.

When you run cargo test, Cargo will compile your project's code, including any unit tests, and run them.

To specialize your tests, you can use various command-line options to control which tests are run. For example:

  • cargo test <test_name> will run only the test with the specified name.
  • cargo test -- --ignored will run only the tests that have been marked with the #[ignore] attribute.
  • cargo test --release will run the tests with optimizations enabled, making them faster and debugging more difficult.

Additionally cargo test after running the specified tests, it will output the results of the tests to the console. The output will include information about which tests passed and failed, as well as any output generated by the tests themselves.

I show how cargo test behaves by starting with an example implementation of a recursive Fibonacci function with a degree n as:

fn fibonacci(n: i32) -> i32 {
    if n < 0 {
        panic!("Fibonacci sequence is only defined for non-negative numbers");
    }
    if n == 0 {
        0
    } else if n == 1 {
        1
    } else {
        fibonacci(n - 1) + fibonacci(n - 2)
    }
}

Adding unit tests is straightforward:

#[test]
fn test_fbonacci_first() {
    assert_eq!(fibonacci(0), 0);
}
#[test]
fn test_fbonacci_fail() {
    assert_eq!(fibonacci(0), -1);
}
#[test]
fn test_fbonacci_rest() {
    assert_eq!(fibonacci(1), 1);
    assert_eq!(fibonacci(2), 1);
    assert_eq!(fibonacci(3), 2);
    assert_eq!(fibonacci(4), 3);
    assert_eq!(fibonacci(5), 5);
    assert_eq!(fibonacci(6), 8);
    assert_eq!(fibonacci(7), 13);
}

cargo test in the RustDevEnv project, and the resulting output is:

The output of cargo test in project RustDevEnv

If you want to get more complicated with your testing, and I hope you do, then please read https://doc.rust-lang.org/book/ch11-01-writing-tests.html.

I have given a taste of what you can do for testing in Rust. Other crates you should look at:

  1. Criterion.rs is an excellent crate for benchmarking your Rust code. It provides statistical analysis of benchmark results and allows you to compare the performance of different implementations.
  2. Proptest is a property-based testing framework that generates random inputs to ensure your code behaves correctly for all possible inputs. It's a powerful tool for catching edge cases and improving the reliability of your code.
  3. If your Rust code depends on external dependencies, Mockers is a great tool for writing tests. It allows you to define mock objects and verify that they're being used correctly.

There are many more Rust test frameworks, and more are being released daily. Please keep evaluating Rust test tools as they appear. I know I will.

Future Work

Where do I start? Rust is an entirely new language ecosystem for me.

Here are a couple of questions I have.

Polar is a big hit with our group. For those unfamiliar with Polars, it is a Rust implementation of Pandas that is fast and solves most of the big data problems that Pandas has. There must be other Python packages that are in Rust. What are they? What new crates can we expect?

Should we use Rust git tools or be locked in with Github actions?

I did not discuss debuggers in this blog article because we are still deciding on debuggers. The internal argument is, do we stick with the IntelliJ Rust plugin debugger? Will it get better?

I am sure there will be more questions. What I like about Rust so far is that it fixes many problems with C. I am uncomfortable with the lack of object-oriented in the core of Rust.

Rust++? No comment.

Remember you get the RustDevEnv and all associated code by cloning the GitHub repo at https://github.com/bcottman/rustDevEnv.

Resources

The Rust Programming Language

Rust Notebooks with Evcxr

JetBrian's (IntelliJ IDEA) Rust Plugin

Mac IDE — Develop Apps & Games on Mac OS

Example Google Style Python Docstrings

Google Python Style Guide

Rust plugin for Pycharm

Install Visual Studio for Mac

Rust with Visual Studio Code

Documentation for Visual Studio Code

crates.io: Rust Package Registry

PyPI

Thanks!

Rust
Jupyter
Ide
Programming
Productivity
Recommended from ReadMedium