OSRust.003: Scalar and Compound Data Types — Integers, Floats, bool, char, Tuples, Arrays, and Type Annotations

Mechatronic anime illustration of a Black systems programmer learning Rust scalar and compound data types in a robotics lab

Core Concepts

  • Rust is statically typed: every value has a type known at compile time, even when the compiler infers that type for you.
  • Rust’s primitive data types divide naturally into scalar types such as integers, floating-point numbers, booleans, and characters, plus compound types such as tuples and arrays.
  • Choosing a type is about range, sign, precision, memory layout, and intended use—not just picking whichever syntax looks shortest.

Rust’s type system is one of the first places where the language begins protecting you before your program ever runs. The compiler must know what kind of value each expression produces so it can reject invalid operations, choose the correct machine representation, and enforce later rules around ownership, borrowing, and APIs.

This lesson follows OSRust.002: Variables. That lesson introduced let, mut, constants, shadowing, scope, and type inference. OSRust.003 focuses on the next question: what types can those variables actually hold?

The official Rust Programming Language book groups the basic built-in types into scalar and compound categories. Scalar values represent one value; compound values group multiple values together.

Tech With Tim introduces Rust’s primitive scalar and compound data types, including integers, floating-point values, booleans, characters, tuples, and arrays.

Rust Is Statically Typed

Static typing means the compiler knows the type of every value at compile time. You do not always need to write the type yourself because Rust can infer many types from context:

fn main() {
    let miners = 128;
    let efficiency = 17.5;
    let online = true;
    let grade = 'A';
}

Here Rust can infer useful defaults from the literals. But inference is not magic: when several types are possible or an API requires a particular type, you may need an explicit annotation.

For example, parsing text can produce many possible numeric types, so the compiler needs additional information:

let count: u32 = "128".parse().expect("Not a number");
A Rust community discussion for beginners centers on the same early challenge: learning the built-in data types and understanding when Rust infers a type versus when the programmer should be explicit.

Scalar Types Represent One Value

Rust’s main scalar families are integers, floating-point numbers, booleans, and characters. A scalar is one logical value, even if the value occupies several bytes in memory.

This distinction becomes important later because collections and user-defined types are built from these smaller pieces. Understanding the primitive layer makes function signatures, structs, enums, indexing, arithmetic, and memory reasoning much easier.

Rust programming language logo and Ferris mascot artwork
Rust programming language artwork from Wikimedia Commons. Credit: Looobay, CC BY-SA 4.0.

Integer Types: Signed, Unsigned, and Sized

Rust provides signed and unsigned integers at several widths. Signed integers can represent negative values; unsigned integers start at zero and use their full range for nonnegative values.

  • i8 / u8
  • i16 / u16
  • i32 / u32
  • i64 / u64
  • i128 / u128
  • isize / usize

The number indicates the bit width. For example, u8 is an 8-bit unsigned integer and i64 is a 64-bit signed integer. isize and usize match the pointer width of the target architecture and are especially common for indexing and sizes.

let temperature: i32 = -5;
let fan_rpm: u16 = 5400;
let rack_index: usize = 7;
let huge_value: u128 = 1_000_000_000_000_000_000;

Integer Literals Can Be Written Several Ways

Rust supports decimal, hexadecimal, octal, binary, and byte literals. Underscores may be used as visual separators without changing the value.

let decimal = 98_222;
let hex = 0xff;
let octal = 0o77;
let binary = 0b1111_0000;
let byte = b'A';

You can also attach a type suffix directly to a numeric literal:

let ports = 65_535u32;
let offset = -12i16;

Integer Overflow Is a Real Constraint

Every fixed-width integer has a finite range. A u8, for example, can represent values from 0 through 255. What happens when arithmetic exceeds the range depends on build mode and the operation used. Debug builds normally panic on overflow; optimized release builds can wrap using two’s-complement behavior when overflow checks are not enabled.

Rust also provides explicit methods such as checked_add, wrapping_add, saturating_add, and overflowing_add so the programmer can state the intended behavior clearly.

let x: u8 = 250;
let safe = x.checked_add(10);      // None
let wrapped = x.wrapping_add(10); // 4
let capped = x.saturating_add(10); // 255
Francesco Ciulla walks through Rust data types with practical examples for integers, floating point, booleans, characters, tuples, arrays, and custom types.

Floating-Point Types

Rust’s primitive floating-point types are f32 and f64. The default is f64 because modern processors usually handle double-precision floating-point arithmetic efficiently and it provides greater precision.

let efficiency = 17.5;       // inferred f64
let voltage: f32 = 12.25;
let precise: f64 = 0.000001;

Floating-point values are approximations of real numbers. Many decimal fractions cannot be represented exactly in binary floating point, so equality checks and accumulated arithmetic need care in scientific, financial, and measurement-heavy software.

Boolean Values

The Boolean type is bool and has exactly two possible values: true and false.

let online: bool = true;
let has_alarm = false;

Booleans are central to conditions, loop control, validation, state flags, and comparisons. Later control-flow lessons will use them constantly.

Rust char Is a Unicode Scalar Value

Rust’s char type is more capable than the one-byte character type many programmers first meet in C. A Rust char represents a Unicode scalar value and occupies four bytes.

let letter: char = 'R';
let lambda = 'λ';
let crab = '🦀';

Single quotes create a char. Double quotes create string data, which is a different topic with different ownership and UTF-8 behavior.

Compound Types Group Multiple Values

Rust’s two fundamental compound types are tuples and arrays. Both group multiple values, but they solve different problems.

Tuples Can Mix Types

A tuple groups a fixed number of values that may have different types:

let miner = ("S21", 200u32, 17.5f64, true);

The tuple’s type here is (&str, u32, f64, bool). You can access values by destructuring:

let (model, hashrate, efficiency, online) = miner;

Or by numeric field position:

println!("{}", miner.0);
println!("{}", miner.2);

Arrays Store One Type at a Fixed Length

An array stores a fixed number of elements, all of the same type. Its length is part of its type.

let temperatures: [i32; 4] = [21, 22, 24, 23];

The type [i32; 4] means “an array of four i32 values.” You can also initialize an array by repeating the same value:

let zeros = [0u8; 16];

Arrays are useful when the number of elements is known at compile time. If you need a growable collection, Rust’s Vec<T> type is usually more appropriate and will be covered later.

Rustfully gives a compact review of scalar and compound Rust types, making it useful after the integer, tuple, and array sections.

Arrays and Tuples Are Different Types

An array is homogeneous: every element has the same type. A tuple may be heterogeneous: each position can have its own type. That makes the two structures useful for different jobs even when they contain the same number of values.

let point_array: [f64; 2] = [10.0, 20.0];
let point_tuple: (f64, f64) = (10.0, 20.0);

These are not interchangeable types. Libraries may choose one or the other based on semantics, trait implementations, indexing, or API design.

Type Annotations Clarify Intent

Rust often infers the type correctly, but explicit annotations can improve correctness and readability when the range, API, protocol, or memory representation matters.

let packet_length: u16 = 1500;
let error_count: u64 = 0;
let temperature_c: f32 = 24.75;
let enabled: bool = true;

Do not add type annotations everywhere just because you can. Use them where inference is ambiguous or where the type communicates something important about the problem domain.

Common Beginner Mistakes

  • Assuming every integer is the same: signedness and width affect range and allowed conversions.
  • Using an unsigned type simply because a value “should never be negative”: arithmetic and subtraction can become less intuitive if the domain can temporarily cross zero.
  • Assuming floating-point values are exact: many decimal values are approximate in binary.
  • Confusing char with a one-byte ASCII value: Rust char stores a Unicode scalar value.
  • Confusing arrays with vectors: arrays have fixed compile-time length; vectors can grow.
  • Assuming tuples and arrays are interchangeable: they have different types and different semantics.
  • Forcing type annotations unnecessarily: let inference work when the intent is already clear.

Practical Exercise

  1. Create a new project with cargo new rust_types_lab.
  2. Declare one signed integer, one unsigned integer, one f32, one f64, one bool, and one char.
  3. Create a tuple that stores a device name, wattage, efficiency value, and online/offline state.
  4. Destructure the tuple into separate variables.
  5. Create an array of five temperature readings.
  6. Print the first and last array elements.
  7. Try changing an explicit integer annotation from u8 to u16 and observe which larger values become legal.
  8. Use checked_add and wrapping_add on a near-maximum u8 value.
  9. Run cargo check, then cargo run.

Knowledge Check + Answers

  1. What does statically typed mean? The type of every value is known to the compiler at compile time.
  2. What are Rust’s four main scalar families? Integers, floating-point numbers, booleans, and characters.
  3. What is the default integer type when context does not force another type? i32.
  4. What is the default floating-point type? f64.
  5. What do usize and isize depend on? The pointer width of the target architecture.
  6. What is special about Rust char? It stores one Unicode scalar value and occupies four bytes.
  7. Can a tuple contain different types? Yes.
  8. Can a Rust array contain elements of different types? No; all elements have the same type.
  9. Is array length part of the array type? Yes.
  10. Why use an explicit type annotation? To resolve ambiguity or communicate required range, precision, representation, or API intent.

Primary References

Elementary Review

Rust needs to know what every value is. Integers represent whole numbers, floating-point types represent approximate fractional values, bool stores true or false, char stores a Unicode scalar value, tuples combine fixed values that may have different types, and arrays store a fixed number of values of one type. Type inference keeps the code concise; explicit annotations make the type clear when the compiler or the reader needs more information.

Editor’s Note

This lesson uses a unique generated 1200×630 mechatronic-anime featured image and a separate internet-sourced Rust body image. Its three distinct YouTube videos are distributed across the lesson rather than stacked together. Standard responsive Gutenberg headings, paragraphs, lists, code, image, and embed blocks are used throughout; no normal lesson text appears in bordered, shaded, card-style, callout, panel, or fixed-width text boxes.

BitcoinVersus.Tech content is provided for informational and educational purposes.

Leave a Reply