Chapter 1

Numbers in Rust

The integer told the float, “You’re being irrational.”
The float replied, “At least I have a point.”

In Rust, numbers don't always get along. Numeric types are kept distinct, and Rust won't let you mix them without an explicit conversion. The types themselves are familiar:

let byte: u8 = 255;           // a single byte, holds 0 to 255
let b: u32 = 42;              // unsigned, so it can't go negative
let a: i32 = -42;             // signed 32-bit, the everyday default
let big: i64 = 9_000_000_000; // 64-bit, for numbers too large for i32
let i: usize = 0;             // the type for sizes and indices
let price: f64 = 19.99;       // floating point (f32 is the smaller one)

These are the numeric types you'll encounter most often. What makes Rust different is how strictly it treats them.

Integer Overflow

Languages handle integer overflow differently: Java wraps, Python's integers grow to hold the result, and C wraps unsigned arithmetic but leaves signed overflow undefined. Rust rejects overflow it detects at compile time by default. At runtime, overflow panics in debug builds and wraps in release builds by default.

let hp: u8 = 200;
let bonus: u8 = 100;

// The compiler detects this overflow and rejects the addition.
// If overflow occurs at runtime, debug builds panic and release builds wrap by
// default.
let total = hp + bonus;

Storing that sum in an unsigned 8-bit integer in C would give 44 instead of 300. Imagine you play Diablo and pick up a bonus item and your health bar suddenly drops from 200 to 44.

Rust's integer methods let you choose what happens when a result does not fit:

If you want the health bar to stop at 255, use saturating_add.

I prefer to choose the overflow behavior explicitly rather than rely on the release default. These methods also give you the same behavior in debug and release builds.

No Implicit Conversions

Rust never mixes numeric types for you. u32 + i32 won't compile, and you can't multiply a u32 by an f64 either. You convert explicitly: as performs a cast that may lose information, .into() handles infallible conversions, and .try_into() returns a Result when a conversion can fail.

let count: u32 = 42;
let price: f64 = 19.99;

// Convert count to f64 before multiplying.
let total = price * count as f64; 

Text into Numbers

Parsing a string can fail because the input might not be a number at all, so parse hands back a Result.

let parsed = "123".parse::<u32>();

The ::<u32> tells parse which number type you want. The result still represents either success or failure, rather than a bare u32.

We'll talk about Result later. For now, .unwrap_or(fallback) extracts a successful value or supplies the fallback if parsing failed. Choose that fallback to match the behavior your caller needs.

Health That Doesn't Wrap Around

A u8 holds 0 to 255. Add 100 to 200 and a debug build panics, while a release build wraps to 44. Neither is what you want from a health bar.

saturating_add is the fix. It does the addition but clamps at the type's maximum instead of overflowing, so stacking buffs tops out at 255 rather than wrapping around.

Vim user? Open Settings in the top bar and turn on Vim Keybindings to use Vim in the exercise editors. Your choice is saved in this browser.

Useful from the Standard Library

Exercise 1 of 3
Open in Web Editor

Results

    Compiler / runtime output
    
                
    Stuck? Show a hint No spoilers, just a nudge
    1. A plain current + gain would panic (debug) or wrap (release) once the sum passes 255.
    2. Look for saturating_add on the integer types: it clamps at the maximum instead of overflowing.
    Reveal the full solution Spoiler: the complete answer
    /// Adds `gain` health to `current`, capping at `u8::MAX` (255) instead of
    /// overflowing.
    ///
    /// `saturating_add` performs the addition but clamps the result at the type's
    /// maximum, so a big stack of buffs tops out at 255 rather than wrapping around
    /// to a small number (which is what a plain `+` would do in a release build).
    fn add_health(current: u8, gain: u8) -> u8 {
        current.saturating_add(gain)
    }
    
    #[test]
    fn test_add_health() {
        assert_eq!(add_health(100, 50), 150);
        assert_eq!(add_health(200, 100), 255);
        assert_eq!(add_health(255, 1), 255);
        assert_eq!(add_health(0, 0), 0);
    }
    

    `damage_with_bonus`

    Rust never converts between numeric types implicitly. If you want to multiply a u32 by an f64, you have to convert one of them first. You can use as for that.

    The function takes base damage as a u32 and an f64 bonus percentage, then returns the final damage as a u32. A bonus of 50.0 means adding half of the base damage again, whether it came from a critical hit, equipment, or some other modifier. Keep the calculation in f64 so the fractional percentage is not lost, then convert the final damage back to u32.

    Converting the final value back to u32 truncates the fractional part toward zero. That matches games which use whole HP, so 8.085 damage becomes 8, not 9.

    Useful Resources

    • as is the cast operator. 1.7_f64 as u32 is 1, not 2, because the cast truncates.
    Exercise 2 of 3
    Open in Web Editor

    Results

      Compiler / runtime output
      
                  
      Stuck? Show a hint No spoilers, just a nudge
      1. The percentage describes the extra damage, not the final damage. What fraction of the base does it add?
      2. base is u32 and bonus_percent is f64, so you can't multiply them directly. Cast base with as f64 first.
      3. To go back to u32 for the return value, use a plain as u32 cast. That truncates the fractional part (drops any fractional HP), so the 15.5% example returns 8.
      Reveal the full solution Spoiler: the complete answer
      /// Applies a damage bonus to a base damage value.
      ///
      /// The bonus is a percentage *added on top* of the base, so `50.0` means
      /// "+50%": a base of `100` becomes `150`. Think of it as a critical-hit bonus,
      /// an equipment buff, or any other damage modifier expressed as a percentage.
      /// Fractional HP is dropped (truncated toward zero), which is what most games
      /// do; half-HP doesn't exist.
      fn damage_with_bonus(base: u32, bonus_percent: f64) -> u32 {
          let multiplier = 1.0 + bonus_percent / 100.0;
          (base as f64 * multiplier) as u32
      }
      
      #[test]
      fn test_damage_with_bonus() {
          // No bonus: damage is unchanged.
          assert_eq!(damage_with_bonus(100, 0.0), 100);
          // Classic +50% bonus (e.g. a critical hit).
          assert_eq!(damage_with_bonus(100, 50.0), 150);
          // +100% = double damage.
          assert_eq!(damage_with_bonus(80, 100.0), 160);
          // Fractional HP truncates toward zero: 7 + 7 * 0.155 = 8.085 → 8, not 9.
          assert_eq!(damage_with_bonus(7, 15.5), 8);
          // 8.75 still truncates to 8; rounding would give 9.
          assert_eq!(damage_with_bonus(7, 25.0), 8);
      }
      

      Parsing Strings into Numbers

      str::parse turns text into the type you ask for. It returns a Result because the input might not be valid for that type.

      Returning 0 on failure is a bad idea in real code because it makes valid input "0" indistinguishable from garbage. In Rust, Option and Result preserve the distinction between valid zero and invalid input. Use Result when a missing value is an error you need to explain. Here, I'll keep error handling out of the exercise: return 0 if the string isn't a valid number.

      u32 can't be negative, so "-5".parse::<u32>() fails and you should return 0 for it too.

      Useful from the Standard Library

      • str::parse turns a string into a type you choose. It returns a Result because the input might not be valid.
      • Result::unwrap_or hands back the value on Ok, or the fallback you give it on Err. Useful for the "just give me a number" path here.
      Exercise 3 of 3
      Open in Web Editor

      Results

        Compiler / runtime output
        
                    
        Stuck? Show a hint No spoilers, just a nudge
        1. parse needs a target type. You can specify it with ::<u32>, or let the function's return type guide inference once the parsed value is returned.
        2. Parsing and handling failure are separate steps. Which method from the introduction lets you choose what happens when parsing fails?
        Reveal the full solution Spoiler: the complete answer
        /// Parses a string into a positive integer. Returns the number if valid, 0 if
        /// invalid.
        fn parse_positive_integer(input: &str) -> u32 {
            input.parse().unwrap_or(0)
        }
        
        #[test]
        fn test_parse_positive_integer() {
            assert_eq!(parse_positive_integer("123"), 123);
            assert_eq!(parse_positive_integer("0"), 0);
            assert_eq!(parse_positive_integer("invalid"), 0);
            assert_eq!(parse_positive_integer("-5"), 0);
        }
        

        Wrapping Up Numbers

        You chose how to handle overflow, converted numeric types explicitly, and used a fallback when parsing failed.

        What We Learned

        • If you add two numbers and the result doesn't fit the type, Rust panics in debug builds and wraps in release builds. When overflow is possible, pick the behavior you want: saturating_add (clamp), checked_add (return an Option), or wrapping_add (wrap around).
        • Rust never mixes numeric types for you. If the types don't match, you must convert them explicitly. as casts may lose information; .into() handles infallible conversions, while .try_into() returns a Result when a conversion can fail.
        • as u32 on a float truncates toward zero (1.7 as u32 is 1); f64::round rounds to the nearest integer. We used truncation for the damage example.
        • str::parse() turns text into a value of the type you ask for. It returns a Result, and .unwrap_or(...) supplies a fallback when parsing fails.
        Next chapter 2Strings, &str, and Chars

        Optional Chapters

        Extra practice to explore at your own pace. These chapters do not count toward course progress.

        ← Return to the Rust course curriculum