5.6 Numeric Literals and Their Default Type

In Rust, numeric literals are used to define values for different numeric types, such as integers and floating-point numbers. One of the key features of Rust’s type system is that it requires numeric types to be explicitly stated or inferred by the compiler, meaning that every literal is assigned a type either based on the context or its default type.

5.6.1 Integer Literals

By default, an integer literal without a suffix is inferred as an i32. However, Rust provides several ways to specify a literal’s type explicitly using suffixes, such as:

  • 123i8 for a signed 8-bit integer
  • 123u64 for an unsigned 64-bit integer

You can also use type annotations when declaring a variable:

#![allow(unused)]
fn main() {
let x = 123u16;        // Literal with a suffix
let y: u16 = 123;      // Type annotation
}

Rust supports the use of underscores to make large numbers more readable:

#![allow(unused)]
fn main() {
let large_num = 1_000_000; // Inferred as i32
}

5.6.2 Floating-Point Literals

Floating-point literals default to f64 for precision and performance reasons. As with integers, the type can be explicitly defined using a suffix, for example:

#![allow(unused)]
fn main() {
let pi = 3.14f32; // 32-bit floating point
let e = 2.718;    // Inferred as f64
}

It's important to note that assigning an integer directly to a floating-point variable, such as let a: f64 = 10;, is invalid in Rust because 10 is treated as an integer literal. Instead, you must use a floating-point literal, like 10.0.

However, floating-point literals can be written without a fractional part. For example, 1. is treated as 1.0, similar to C:

#![allow(unused)]
fn main() {
let x = 1.;  // Equivalent to 1.0
}

Unlike in C, Rust does not allow omitting the digit before the decimal point. Therefore, .7 is not a valid floating-point literal in Rust. Instead, you must write it as 0.7: This requirement ensures clarity in floating-point literals, avoiding potential confusion in code.

5.6.3 Hexadecimal, Octal, and Binary Literals

Rust supports other number systems for literals, which can be useful for low-level programming:

  • Hexadecimal: Prefix with 0x
    • Example: let hex = 0xFF;
  • Octal: Prefix with 0o
    • Example: let octal = 0o77;
  • Binary: Prefix with 0b
    • Example: let binary = 0b1010;

Example:

fn main() { // editable example
    let decimal = 255;
    let hex = 0xFF;
    let octal = 0o377;
    let binary = 0b1111_1111;
    let byte = b'A';  // Byte literal

    println!("Decimal: {}", decimal);
    println!("Hexadecimal: {}", hex);
    println!("Octal: {}", octal);
    println!("Binary: {}", binary);
    println!("Byte: {}", byte);
}

5.6.4 Type Inference

While Rust allows type inference, it's important to note that certain operations may require explicit type annotations, especially in cases where a literal could be interpreted in multiple ways.

Example:

fn main() {
    let x = 42;         // Inferred as i32
    let y = 3.14;       // Inferred as f64
    let z = x as f64 + y; // Type casting x to f64
    println!("Result: {}", z);
}

In this example, we cast x to f64 to match the type of y for the addition operation.