Unlock Your Python Backend Career: Build 30 Projects in 30 Days. Join now for just $54

Python Devs Beware: The % Operator Lies in Rust.

by Ugochukwu Chizaram Omumusinachi

.

Updated Mon Sep 08 2025

.
Python Devs Beware: The % Operator Lies in Rust.

Looking at these two code samples below, they look the same, right? What could possibly go wrong?

Python code snippet


print(5 % 3)    # 2
print(-5 % 3)   # 1
print(5 % -3)   # -1
print(-5 % -3)  # -2 

Rust code snippet


fn main() {
    println!("{}", 5 % 3);    // 2
    println!("{}", -5 % 3);   // -2
    println!("{}", 5 % -3);   // 2
    println!("{}", -5 % -3);  // -2
}

Well, everything. You see, the Rust code is fooling you, because Rust does not handle the % operator the way Python does. In fact, it doesn’t even calculate the modulo in the sense you might expect.

In simple terms, Python handles % as a true modulo operator, where the result always follows the divisor’s sign. Rust, however, treats % as the remainder operator, where the result always follows the dividend’s sign.

In this article, we’ll explore the differences, why Rust does it the way it does, and the correct way to get a true modulo in Rust while preserving signs.

The math behind it

The modulo operator as you know it is just a native implementation of a simple arithmetic theory in code instructions.

Under the hood, in Python the modulo operator could be defined as:


def euclidean_mod(a: int, b: int) -> int:
    q = a // b           # floor division
    r = a - b * q
    return r

Whereas in Rust, the % operator follows a different rule:


fn remainder(a: i32, b: i32) -> i32 {
    let q = a / b;       // truncation division
    let r = a - b * q;
    r
}

The core difference is the type of division formula they use. Modulo is always “the remainder,” but the way you pick the quotient changes the remainder’s sign.

  • In Rust, by default, we use truncated division. That means the quotient is chopped toward zero, and the remainder follows the sign of the dividend.

  • In Python, the % operator uses Euclidean division. That means the quotient is floored instead of truncated, and this tiny shift ensures the remainder is always non-negative when the divisor is positive.


You see, before we even dive into the numbers, the key is simple: both calculate remainders correctly — but Python goes one step further. It “corrects” the result by flooring the quotient, which guarantees the remainder falls into the clean range:

0≤r<∣b∣0 \leq r < |b|0≤r<∣b∣

It’s a small detail, but it completely changes how % behaves with negatives.

But why the difference

Well, it is just a conceptual choice. Rust comes from the same world as C, Go, and Java — where % is treated as a remainder operator that simply truncates the division and leaves the remainder “as is.” This makes sense in low-level systems programming because it’s fast, predictable, and lines up with how hardware division usually works.

Python on the other hand tries to stay closer to mathematics. In math, Euclidean division is what makes sense for modular arithmetic — the remainder always lands neatly in the range 0 ≤ r < |b|. That’s why in Python % feels more “intuitive” for things like clock math, cycles, and array indexing.

So, neither language is “wrong.” They just made different design decisions. Rust chose performance and tradition. Python chose mathematical neatness.


Fixing this problem in Rust

Now here’s the good news: Rust actually gives you the Python behavior too — you just have to call it by name. It’s called .rem_euclid().

Example:


fn main() {
    println!("{}", -5 % 3);              // -2 (truncated)
    println!("{}", -5_i32.rem_euclid(3)); // 1 (Euclidean, same as Python)
}

So what is .rem_euclid() really? It’s not some magic. It’s simply another method defined in 

Rust’s RemEuclid trait — which is implemented for signed integers (and unsigned too). That’s why you can just call it directly on any integer type (i32, i64, etc.).

This lets you pick exactly how you want division to behave: % for truncated remainder, or .rem_euclid() for Euclidean.

Summary and Cheatsheet

  • In Rust, % uses truncation. This means remainders can be negative.

  • If you don’t want that, use .rem_euclid() instead.

  • For simple cases where your dividend is always positive, % is fine.

  • Be aware: .rem_euclid() can panic if you pass in a zero divisor (same as %).

In Python, % is always Euclidean — it floors the quotient so that the remainder behaves nicely without you worrying about it.

  • Note : If you are using % in a Rust codebase where negative numbers might appear — please review your code. Leaving it as is can give you very big problems later.

If you learned something new here or have any questions, feel free to message me on my linkedin.

See you next week!!

Course image
Become a Rust Backend Engineeer today

All-in-one Rust course for learning backend engineering with Rust. This comprehensive course is designed for Rust developers seeking proficiency in Rust.

Start Learning Now

Whenever you're ready

There are 4 ways we can help you become a great backend engineer:

The MB Platform

Join 1000+ backend engineers learning backend engineering. Build real-world backend projects, learn from expert-vetted courses and roadmaps, track your learnings and set schedules, and solve backend engineering tasks, exercises, and challenges.

The MB Academy

The “MB Academy” is a 6-month intensive Advanced Backend Engineering BootCamp to produce great backend engineers.

Join Backend Weekly

If you like post like this, you will absolutely enjoy our exclusive weekly newsletter, Sharing exclusive backend engineering resources to help you become a great Backend Engineer.

Get Backend Jobs

Find over 2,000+ Tailored International Remote Backend Jobs or Reach 50,000+ backend engineers on the #1 Backend Engineering Job Board

Backend Tips, Every week

Backend Tips, Every week