Why does abs() return signed integer?

This abs() returns i32, but absolute values are always equal to or greater than 0, why does it return i32?

I suspect that's because abs() is just a shorthand for multiplying by -1 if the value is negative.

Changing from i32 to u32 would mean the range of representable numbers changes, and it might also make things more awkward because your code no longer uses the same type everywhere.

For example, imagine x + y.abs() - z where x, y, and z are all i32 - this wouldn't compile because you can't add i32 to u32.

fn main() {
    let x: i32 = 1;
    let y: i32 = -2;
    let z: i32 = 5;
    let _ = x + abs(y) - z;
}

fn abs(n: i32) -> u32 {
    if n < 0 {
        -n as u32
    } else {
        n as u32
    }
}
error[E0308]: mismatched types
 --> src/main.rs:5:17
  |
5 |     let _ = x + abs(y) - z;
  |                 ^^^^^^ expected `i32`, found `u32`

error[E0277]: cannot add `u32` to `i32`
 --> src/main.rs:5:15
  |
5 |     let _ = x + abs(y) - z;
  |               ^ no implementation for `i32 + u32`
  |
  = help: the trait `Add<u32>` is not implemented for `i32`
  = help: the following other types implement trait `Add<Rhs>`:
            <&'a i32 as Add<i32>>
            <&i32 as Add<&i32>>
            <i32 as Add<&i32>>
            <i32 as Add>

Some errors have detailed explanations: E0277, E0308.
For more information about an error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors
3 Likes

Note that there is also unsigned_abs().

13 Likes

The code on GitHub is human-readable, but there's a "source" button near that abs() , click on the "source" button, it will show code that's not human-readable.

1 Like

That's because most of the impl blocks for integer types are generated by macro because they are similar. The code for the function on GitHub is inside the macro_rules! definition for the macro, which starts at the top of the file. The documentation links to the invocation of the macro where it generates the specific implementation for i32.

Yep. The standard library presents essentially the same API for all integer types, so they use a macro to generate all the methods rather than risking typos with copy/paste.

As far as the compiler is concerned, the abs() method really is defined in that int_impl!() invocation, but you won't be able to see what the macro generates unless you run cargo expand and go spelunking through the expanded code.

Because the convention for a long time in programming languages has been that operations on things return the same type as the input. Note that u32 is also "wrong", since the absolute value of an i32 can't actually be u32::MAX either.

The nice solution is to have integer types with arbitrary ranges. So then you can have

abs(Int<-2³¹, 2³¹ - 1>) → Int<0, 2³¹>

But that requires that the programming language have an actually good integer type system, which almost no languages do.

3 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.