Did you know that the efficiency of your linter can significantly affect your productivity?
After adding a small feature to a big project I was working on recently, I needed to use the configured linter to align my addition with the style of the rest of the code.
So I ran the linter.
30 seconds in…no progress bar.
Me: “It’s been a while. Let me get something to drink and be back.”
Two minutes in.
Me: “It’s not done yet? It’s been quite a long time, but it should finish up soon.”
Five minutes in.
Me: “It’s still running? Is that why there’s no progress bar? I probably didn’t look at how to add the progress bar, but I’m already in too deep to start all over again with the progress bar.”
Nine minutes in.
Me, holding my drink, staring into the abyss: “…did I break it?”
Linter: Completes
Me: “So NOW you’re done!?”
From the look of things, a faster linter would have been less frustrating. Imagine having to run the linter multiple times. I, like many others, have done so, and we’ve lost our flow and gotten distracted in the process.
That’s why I’m particularly excited to help you enjoy your next beverage in peace by writing this article about a tool called Ruff, a linter and code formatter that should help us avoid this problem going forward.
We’ll start with what Ruff is and how it solves inefficiencies. We’ll then look at how to set it up and use it. Finally, we’ll do a formal comparison of Ruff with similar tools to understand just how well it performs.
Before jumping into Ruff, let’s explain what linters and code formatters are. If you’re already familiar with this, then go ahead and skip to the setup section.
Linters and code formatters are tools that tend to get slower as your projects get bigger. A linter scans a codebase to check for potential bugs and errors, enforces consistent styling throughout your codebase, and improves code quality. A code formatter scans your codebase and restructures components to fit any style you describe and enforce consistency throughout your codebase.
Ruff is a Python linter and code formatter. What sets it apart from other linters and code formatters is that it’s built in Rust, meaning that it performs well and is more efficient. Ruff also lints and formats code in large projects very quickly, so quick that I had to double check that Ruff was performing any linting or formatting during my benchmarks. (I didn’t even have enough time to finish my drink!).
To add to the list of Ruff’s features, it supports over 800 lint rules. Many of the rules are inspired by other linters like Flake8, Pylint, isort, pyupgrade, and many more, making it easier to use Ruff in place of these linters without extensively translating your linter’s configuration to work with Ruff.
Now that we’ve done a little look into Ruff, it’s time to learn how to set up and use it in your project.
There are multiple methods that to install Ruff. You’re fine picking the method that suits your needs, preferences, or taste.
Installing with pip
:
pip install ruff
If you prefer using your OS package manager, you can for example use these:
# macOS brew install ruff # Arch Linux pacman -S ruff # Alpine linux apk add ruff
You can also install Ruff through conda
:
conda install -c conda-forge ruff
Or manually, using standalone installers:
# On macOS and Linux. curl -LsSf https://astral.sh/ruff/install.sh | sh # On Windows. powershell -c "irm https://astral.sh/ruff/install.ps1 | iex" # For a specific version. curl -LsSf https://astral.sh/ruff/0.5.0/install.sh | sh powershell -c "irm https://astral.sh/ruff/0.5.0/install.ps1 | iex"
Note: Versions 0.5.0 upwards are the only versions of Ruff available through the standalone installer.
After installing Ruff, you’d have to configure it if you want to lint or format your code in a specific way. Ruff allows you to configure its linting and formatting through the pyproject.toml
or ruff.toml
files.
pyproject.toml
:
# pyproject.toml [tool.ruff] line-length = 88 exclude = ["build/", "docs/"] [tool.ruff.lint] select = ["E", "F", "UP", "B", "SIM", "I"]
ruff.toml
:
line-length = 88 exclude = ["build/", "docs/"] [lint] select = ["E", "F", "UP", "B", "SIM", "I"]
These configurations set the maximum line length to 88 characters, exclude the build/
and docs/
directories from linting and formatting, and enable the pycodestyle (E), Pyflakes (F), pyupgrade (UP), flake8-bugbear (B), flake8-simplify (SIM), and isort (I) lint rules.
Now, let’s look into using Ruff as a linter and as a code formatter:
ruff check
is the primary way to lint Python files in the current directory (excluding specified files in your configuration). Running ruff check
in the terminal lints all Python files in the current directory.
Sometimes, ruff check
finds automatically fixable errors in your codebase. If you want ruff check
to fix all the fixable errors it finds, run the command with the --fix
flag:
ruff check --fix
To lint all the files in the current directory and re-lint any that changes, add the --watch
flag:
ruff check --watch
To lint a specific file, add the path as an argument to ruff check
:
ruff check path/to/specific/file.py
Running ruff format
in the terminal formats all Python files in the current directory.
To format a specific file, add its path as an argument to ruff format
:
ruff format path/to/file.py
You can also pass a path directory as an argument to ruff format
if you wish to format all the files in that directory:
ruff format path/to/directory/
Now you know how to use Ruff as both a linter and formatter, and you’re all set to use Ruff in your project.
In this section, we’ll compare Ruff to Flake8, Black, and Pylint in terms of performance, configuration process, community, and support.
I ran a simple benchmark to compare these tools, and here are the results:
# pygame snake game (small code base) Ruff (linting) took 1.0 seconds Flake8 (linting) took 2.0 seconds Pylint (linting) took 1.0 seconds Ruff (formatting) took 0 seconds Black (formatting) took 9.0 seconds # cpython (large code base) Ruff (linting) took 1.0 seconds Flake8 (linting) took 19.0 seconds Pylint (linting) took 2.0 seconds Ruff (formatting) took 0 seconds Black (formatting) took 443.0 seconds
I’ve created a GitHub repository for the benchmarking scripts and codebases if you wish to run it yourself or take a look at how I made the benchmark.
From the results, you can see that Ruff performs very well compared to the other linters and code formatters in the benchmark. In the linting category, Ruff is followed by Pylint, which falls behind as the project gets bigger, and then Flake8, which falls further behind as the project gets bigger.
Black is the only other formatter in this benchmark. This benchmark shows that Ruff is the better-performing formatter. Ruff takes less than one second to format the codebase regardless of the size, compared to Black, which took nine seconds on the smaller code base and took almost 50x the time in a codebase as large as CPython.
However, you should note that the benchmark doesn’t account for how different each tool operates by default. This may affect how much work each tool does during the benchmark. Nevertheless, it’s a good starting point to see how well these tools scale up in bigger projects.
Right out of the box, Ruff, Pylint, Black, and Flake8 have default settings that allow you to use them without configuration. However, you may want to configure the tools to operate based on your tastes.
I’ve written simple and identical configurations for these tools. Here’s how they look.
Ruff (ruff.toml
):
line-length = 88 exclude = ["__pycache__", "build", "dist", ".git"] [lint] select = ["E", "F", "W"]
Flake8 (.flake8
):
[flake8] max-line-length = 88 exclude = .git,__pycache__,build,dist
Pylint (.pylintrc
):
[MASTER] ignore=__pycache__,build,dist,.git max-line-length=88
These configurations set the maximum allowed line to a length of 88 and exclude the .git
, __pycache__
, build
, and dist
directories from being linted. In Ruff’s configuration file, the select
option specifies which linting rules or categories of rules to enable: E
and W
(for error and warning) from Pylint, and F
from Pyflakes.
Ruff lets you configure its linting and formatting rules in the ruff.toml
file. You can also configure Ruff within the project’s general configuration file: pyproject.toml
.
Flake8 allows you to specify options for linting as part of the command line arguments to the flake8
command.
If you don’t want to specify the options every time you’re calling the flake8
command, you can configure those options via files like .flake8
, setup.cfg
, and tox.ini
(as we saw in the list above). Let’s look at a command-line arguments version of the configuration from the list earlier:
flake8 --max-line-length 88 --exclude .git,__pycache__,build,dist
Writing options as arguments is helpful if you need to specify configurations you want to run once or a few times without editing the configuration file.
Flake8 also allows you to use and configure plugins, which while allowing you to get more specific functionality, can add complexity to the overall configuration.
Pylint has a more complex configuration due to its extensive rule set. It is well-supported compared to Ruff but requires a much more complex setup process. As of writing this, I struggle with getting Pylint working with virtual environments.
Now, let’s look at simple configuration for formatting in Ruff and Black.
Black (pyproject.toml
):
[tool.black] line-length = 88 exclude = '/(\.git|\.venv|_build|build|dist|__pycache__)/'
Ruff (pyproject.toml
):
[tool.ruff] line-length = 88 exclude = ["__pycache__", "build", "dist", ".git", ".venv", "_build"]
These configurations ensure that all lines in the Python scripts to be formatted must be at most 88 lines and exclude the .git
, __pycache__
, build
, _build
, dist
, and venv
files and folders from being formatted.
Ruff and Black work out of the box with good defaults, formatting the codebase in similar styles. Unlike Ruff, Black focuses on formatting, meaning you need to combine it with a linter to get linting benefits. Like Ruff, you can configure Black using the pyproject.toml
file.
Ruff, Pylint, Black, and Flake8 had their initial releases in 2022, 2001, 2019, and 2014 respectively. Ruff being newer in comparison means that it has had less time to grow compared to Flake8, Black, and Pylint. This also means that you may not see as much support to help you achieve specific goals in Ruff compared to these tools.
However, Ruff’s community is growing rapidly and has a high adoption rate. Even major projects like Apache Airflow and FastAPI use Ruff in managing their projects, meaning that Ruff has a promising future.
Ruff being able to format and lint a codebase also means that you don’t need an extra setup to get these benefits of a linter and code formatter together. With Ruff’s configuration system, setting up linting and code formatting for your projects is easy, and being built for performance means you can use Ruff in larger projects.
Ruff’s growing community and adoption rate make it a good choice for linting and formatting because you don’t have to worry about losing support in the future. Taking its growth rate into account its support in the future looks promising.
Debugging Rust applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking the performance of your Rust apps, automatically surfacing errors, and tracking slow network requests and load time, try LogRocket.
LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Rust application. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app’s performance, reporting metrics like client CPU load, client memory usage, and more.
Modernize how you debug your Rust apps — start monitoring for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowHandle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.
Design React Native UIs that look great on any device by using adaptive layouts, responsive scaling, and platform-specific tools.
Angular’s two-way data binding has evolved with signals, offering improved performance, simpler syntax, and better type inference.