Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Rust (2024 edition)  Module 2 – Values, types, functions and control flow

Tuples, arrays and type conversions

Group values in tuples and arrays, see what an index past the end does, and convert numbers with as, From and TryFrom, knowing what each can lose.

  • Beginner
  • 20 minutes
  • Examples run with Rust 1.99.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A tuple groups a fixed number of values of any types · An array's length is part of its type, and every index is checked · Number types never convert by themselves. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Create, read and destructure tuples, and swap two values with one tuple assignment
  • Create fixed-size arrays, read them safely with get, and explain what an index out of range does
  • Convert between number types with as, From and TryFrom, and predict when as loses information
  • Choose the conversion that fits a value, and report the value that did not fit

Before you start

What you’ll learn · Before you start

What you will learn

  • Create, read and destructure tuples, and swap two values with one tuple assignment
  • Create fixed-size arrays, read them safely with get, and explain what an index out of range does
  • Convert between number types with as, From and TryFrom, and predict when as loses information
  • Choose the conversion that fits a value, and report the value that did not fit

Before you start

On this page

300i32 as u8 is 44, and rustc builds that line without a warning. Rust never turns one number type into another on its own, but how you ask decides what can be lost: as always produces a value, From exists only where nothing can be lost, and TryFrom checks and can fail.

First, though, the lesson groups values: in tuples, which mix types, and in arrays, whose length is part of their type and whose every index is checked. Every result on the page is a run on Rust 1.99.0.

A tuple groups a fixed number of values of any types

A post office’s city and postal code belong together but have different types, a &str and a u32. A tuple: A value that groups a fixed number of values, each of its own type, such as (&str, u32). Its values are read by position, .0 and .1, or taken apart all at once with a pattern. holds both:

tuples.rs: making, reading and taking apart a tuple Rust · tuples.rs
// A tuple groups a fixed number of values, each of its own type.
fn main() {
    let office = ("Pune", 411_001u32);
    println!("{} has postal code {}", office.0, office.1);

    let (city, code) = office;
    println!("destructured: {city}, {code}");

    let mut first = "Asha";
    let mut second = "Ravi";
    (first, second) = (second, first);
    println!("after the swap: {first}, {second}");

    let nothing = ();
    println!("the unit value: {nothing:?}, {} bytes", std::mem::size_of_val(&nothing));
    println!("the whole tuple: {office:?}");
}

Output

Pune has postal code 411001
destructured: Pune, 411001
after the swap: Ravi, Asha
the unit value: (), 0 bytes
the whole tuple: ("Pune", 411001)

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O tuples.rs && ./tuples

Rust tuples have no field names: .0 and .1 read the values by position. let (city, code) = office; destructures the tuple into two bindings at once, and the same pattern on the left of an assignment swaps two values in one line, (first, second) = (second, first). Rust tuples of up to twelve values print with {:?} and compare with ==; the last line printed one.

The empty tuple, (), is called the unit type: The type of the empty tuple, written (), with exactly one value, also written (). It takes no memory, and it is what a function returns when it returns nothing.. It has one value, also written (), takes no bytes, and is what a function returns when it returns nothing, which the next lesson shows.

An array’s length is part of its type, and every index is checked

An array holds a fixed number of values of one type, and its type says how many: four marks are a [u32; 4]. [0u8; 8] repeats a value eight times:

arrays.rs: making, copying and reading an array Rust · arrays.rs
// An array: a fixed number of values of one type, with the length in its type.
fn main() {
    let marks: [u32; 4] = [72, 85, 64, 91];
    let empty_row = [0u8; 8];
    println!("{} marks: {marks:?}", marks.len());
    println!("a row of {} empty seats: {empty_row:?}", empty_row.len());

    let mut copy = marks;
    copy[0] = 100;
    println!("marks: {marks:?}, copy: {copy:?}");

    let mut total = 0;
    for mark in marks {
        total += mark;
    }
    println!("total: {total}");
    println!("marks.get(2) = {:?}, marks.get(9) = {:?}", marks.get(2), marks.get(9));
}

Output

4 marks: [72, 85, 64, 91]
a row of 8 empty seats: [0, 0, 0, 0, 0, 0, 0, 0]
marks: [72, 85, 64, 91], copy: [100, 85, 64, 91]
total: 312
marks.get(2) = Some(64), marks.get(9) = None

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O arrays.rs && ./arrays

Assigning an array copies it when its values can be copied, as numbers can, so changing copy left marks as it was. for mark in marks takes each value in turn, and get(9) answered None for a position past the end instead of stopping the program. Rust arrays never grow; a list whose length changes is a Vec, which the collections module covers. Indexing with [] is checked too. When the compiler can see the index, it refuses a bad one:

const_index.rs: an index rustc can check Rust · const_index.rs
// An index the compiler can check.
fn main() {
    let marks = [72, 85, 64, 91];
    println!("the fifth mark: {}", marks[4]);
}

Output (exit status 1)

Printed as an error (standard error)

error: this operation will panic at runtime
 --> const_index.rs:4:36
  |
4 |     println!("the fifth mark: {}", marks[4]);
  |                                    ^^^^^^^^ index out of bounds: the length is 4 but the index is 4
  |
  = note: `#[deny(unconditional_panic)]` on by default

error: aborting due to 1 previous error

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O const_index.rs && ./const_index

When the index is only known while the program runs, the check runs then. This loop, meant to add up the four marks, runs one position too far:

sum_marks.rs: one position too many Rust · sum_marks.rs
// Meant to add up four marks, with a range that runs one position too far.
fn main() {
    let marks = [72, 85, 64, 91];
    let mut total = 0;
    for i in 0..=marks.len() {
        total += marks[i];
    }
    println!("total: {total}");
}
sum_marks.rs built and run Python · index_session.py
from session import run

# sum_marks.rs builds, because the index is only known while it runs; the run stops at the bad index.
run("""
rustc --edition 2024 sum_marks.rs
!./sum_marks
""")

Output

$ rustc --edition 2024 sum_marks.rs
$ ./sum_marks

thread 'main' (…) panicked at sum_marks.rs:6:18:
index out of bounds: the len is 4 but the index is 4
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[exit status 101]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 index_session.py

Step through the loop and the check that stopped it:

Interactive · message by message Every marks[i] in sum_marks.rs passes a bounds check Step through the loop. Watch the check that runs before each read, and the position where it stops the program.
Every marks[i] in sum_marks.rs passes a bounds check, step 6 of 6. Messages so far: for i in 0..=len to The bounds check: marks[0]; The bounds check to marks, 4 values: read position 0; for i in 0..=len to The bounds check: marks[1], [2], [3]; for i in 0..=len to The bounds check: marks[4].for i in 0..=lenfor mark in marksThe bounds checknothing to checkmarks, 4 values72, 85, 64, 91marks[0]read position 0marks[1], [2], [3]marks[4]

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Five positions, four values. 0..=marks.len() is the range 0 to 4 with 4 included: it gives five positions, 0, 1, 2, 3 and 4, for an array of four values.
  2. Position 0. marks[0] first goes to the bounds check: 0 is below the length, 4, so the read goes ahead.
  3. The read. The value at position 0 is read and added: the total is 72.
  4. Positions 1 to 3. Positions 1, 2 and 3 pass the same check and add 85, 64 and 91. The total is 312, the right answer, but the range is not finished.
  5. Position 4. marks[4] reaches the check, and 4 is not below 4. The check stops the program with a panic before anything past the array is read, so total: … is never printed.
  6. The fix. 0..marks.len() leaves 4 out, so the loop reads positions 0 to 3. Better still, for mark in marks reads each value with no index to get wrong.

That bounds check: The check that an index is inside a list before the value there is read. In safe Rust a failed check stops the program with a panic, so nothing past the end is ever read. makes a read past the end impossible in safe Rust: the program stops with a panic: Rust's way of stopping a program, or one thread of it, at an error it cannot carry on from, such as an index past the end of a list. It prints the reason and the line, and a program whose main thread panics exits with status 101. More in “Why Rust: safety, speed and where it runs” instead. Here are tuples and arrays side by side:

Property Tuple Array
Values any types, each its own all of one type
Length fixed, part of the type fixed, part of the type
Read a value .0, .1: positions fixed in the code [i] or get(i): a position worked out while running
Loop over it no yes
Typical use a few values that belong together many values of the same kind

Checkpoint

Check: Tuples and destructuring · Arrays and checked indexes

  1. Question 1 of 3 After let office = ("Pune", 411_001u32);, which line prints the postal code?

    Choose one answer.

    Show the answer: Question 1 of 3

    Answer: println!("{}", office.1);

    A tuple's values are read by position, starting at 0, or taken apart all at once with let (city, code) = office;.

  2. Question 2 of 3 sum_marks.rs loops over 0..=marks.len() for four marks. What happens when it runs?

    Choose one answer.

    Show the answer: Question 2 of 3

    Answer: It adds the four marks, then panics at position 4 with "index out of bounds"

    0..=4 gives five positions for four values. The check before marks[4] fails, and the program panics with "index out of bounds: the len is 4 but the index is 4". Use 0..marks.len(), or better for mark in marks.

  3. Question 3 of 3 let mut copy = marks; then copy[0] = 100;, where marks is [72, 85, 64, 91]. What is marks[0] afterwards?

    Choose one answer.

    Show the answer: Question 3 of 3

    Answer: 72, because assigning the array copied all four values

    Arrays live inline and are copied by assignment when their values can be copied, as numbers can. Changing the copy leaves the original as it was.

Number types never convert by themselves

Rust has no implicit conversions between number types, not even to a wider one where nothing could be lost. This program assigns a u8 to a u32 and a u32 to a u8:

no_implicit.rs: two conversions nobody wrote Rust · no_implicit.rs
// A wider and a narrower number type, with no conversion written.
fn main() {
    let seats: u8 = 200;
    let booked: u32 = 1_000;
    let total: u32 = seats;
    let shown: u8 = booked;
    println!("{total} {shown}");
}

Output (exit status 1)

Printed as an error (standard error)

error[E0308]: mismatched types
 --> no_implicit.rs:5:22
  |
5 |     let total: u32 = seats;
  |                ---   ^^^^^ expected `u32`, found `u8`
  |                |
  |                expected due to this
  |
help: you can convert a `u8` to a `u32`
  |
5 |     let total: u32 = seats.into();
  |                           +++++++

error[E0308]: mismatched types
 --> no_implicit.rs:6:21
  |
6 |     let shown: u8 = booked;
  |                --   ^^^^^^ expected `u8`, found `u32`
  |                |
  |                expected due to this
  |
help: you can convert a `u32` to a `u8` and panic if the converted value doesn't fit
  |
6 |     let shown: u8 = booked.try_into().unwrap();
  |                           ++++++++++++++++++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0308`.

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O no_implicit.rs && ./no_implicit

Both lines are E0308, mismatched types, and each help line suggests one of Rust’s type conversions for its direction. For the wider type, .into(): a conversion that cannot fail. For the narrower one, .try_into(), which can fail, with .unwrap() to stop the program if it does. These are the three type conversions for numbers:

  • as converts any number type to any other and never fails, but it may lose information.
  • From, or .into(), exists only where every value fits, so it cannot lose information.
  • TryFrom, or .try_into(), checks the value and returns an error when it does not fit.

Here are all three on values that fit and values that do not:

conversions.rs: From, TryFrom and as Rust · conversions.rs
// One value converted three ways: From, TryFrom and as.
fn main() {
    let small: u8 = 200;
    let big: i32 = 300;
    println!("u32::from(200u8)     = {}", u32::from(small));
    println!("u8::try_from(300i32) = {:?}", u8::try_from(big));
    println!("300i32 as u8         = {}", big as u8);
}

Output

u32::from(200u8)     = 200
u8::try_from(300i32) = Err(TryFromIntError(PosOverflow))
300i32 as u8         = 44

Recorded with rustc 1.99.0 (b940084d7) on macOS 26 arm64. To run it yourself: rustc --edition 2024 -O conversions.rs && ./conversions

u32::from(200u8) gave 200, because every u8 fits a u32. u8::try_from(300i32) gave an error that even says which way it overflowed. 300i32 as u8 gave 44 and no error at all: the value silently became another number.

Key idea

Number types never convert on their own: write From or .into() where every value fits, TryFrom where one may not, and as only where you have decided what a value that does not fit should become.

Checkpoint

Check: as, From and TryFrom

  1. Question 1 of 2 let total: u32 = seats; fails with E0308, where seats is a u8. Which fix does the compiler suggest, and why does it fit?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: seats.into(), because every u8 fits a u32, so the conversion cannot fail

    Rust never converts number types by itself. For a conversion that cannot lose information, From and into exist; for one that can, try_into checks.

  2. Question 2 of 2 Which of the three conversions can turn a value into a different number without any error?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: as

    as always produces a value of the new type, even when it is a different number. From cannot lose information, and TryFrom reports the values it cannot convert.

Every kind of as cast, measured, and what each loses Pro: opens with a Pro, Premium or Ultimate pass

as converts between any two number types, and between numbers, chars and bools, and it never fails. This part runs every kind of cast, shows what happens to the bits, and ends with when as is still the right tool.

In this part:

  • What an integer cast does to the bits, cast by cast
  • Floats cast to integers: too large, too small and NaN
  • Integers cast to floats, and chars cast to numbers
  • When as is the right tool, and what to use instead

1 diagram · 2 runnable examples (Rust) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

From and TryFrom in practice, with an exercise Pro: opens with a Pro, Premium or Ultimate pass

From and TryFrom turn a conversion into something the compiler checks: lossless, or checked with an error that says what went wrong. This part shows where each exists, what their errors say, what newer releases added, and an exercise that converts four values or names the one that failed.

In this part:

  • From exists only where nothing can be lost
  • TryFrom returns an error that says which way it failed
  • Three ways to handle a conversion that can fail
  • Conversions that newer releases added
  • Exercise: four values into a [u8; 4]

1 diagram · 4 runnable examples (Rust) · about 6 minutes

Opening this part with your pass…Pro part. It opens with the same pass as the part above.

Practise: tuples, arrays and conversions

Questions on tuples, arrays, bounds checks and the three conversions; each answer you pick is explained at once.

Programmer Calculator (Bitwise) Type 300 and look at its low 8 bits, the 44 that as u8 keeps.

Practice that adapts to you

Practise this lesson

11 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 11 What is the type of ("Pune", 411_001u32)?

    Choose one answer.

    Show the answer: Question 1 of 11

    Answer: (&str, u32)

    A tuple's type is the types of its values in order. ("Pune", 411_001u32) is a (&str, u32).

  2. Question 2 of 11 let mut a = 1; let mut b = 2; then (a, b) = (b, a);. What are a and b?

    Choose one answer.

    Show the answer: Question 2 of 11

    Answer: a is 2 and b is 1

    The right side builds a tuple of the current values, then the pattern on the left assigns its parts, so a tuple assignment swaps two values in one line.

  3. Question 3 of 11 What is ()?

    Choose one answer.

    Show the answer: Question 3 of 11

    Answer: The empty tuple, the unit type, which takes no bytes

    The unit type has exactly one value, also written (), and takes no space. It is what a function returns when it returns nothing.

  4. Question 4 of 11 A function must return the smallest and the largest of a list of marks, and another stores the reading of each of 24 hours. Which types fit?

    Choose one answer.

    Show the answer: Question 4 of 11

    Answer: A tuple (u32, u32) for the two results, and an array [f64; 24] for the readings

    A tuple groups a few values with fixed meanings, even of different types. An array holds many values of one type that the code loops over or indexes.

  5. Question 5 of 11 What is the type of [0u8; 8]?

    Choose one answer.

    Show the answer: Question 5 of 11

    Answer: [u8; 8], an array of eight u8 values

    [value; N] makes an array of N copies of value, and its type is [T; N]: a [u8; 8] here.

  6. Question 6 of 11 For let marks = [72, 85, 64, 91];, what does marks.get(4) return?

    Choose one answer.

    Show the answer: Question 6 of 11

    Answer: None

    get returns Some of the value for a position inside the array and None for one outside it. Use it when a position may be out of range and you want to decide what happens.

  7. Question 7 of 11 A program indexes four marks with the literal marks[4]. What happens?

    Choose one answer.

    Show the answer: Question 7 of 11

    Answer: rustc refuses to build it, saying the operation will panic at run time

    An index the compiler can check is checked while the program is built: const_index.rs was refused with "this operation will panic at runtime". Indexes known only later are checked when they are used.

  8. Question 8 of 11 Which change makes sum_marks.rs add up the four marks without a panic, with no index to get wrong at all?

    Choose one answer.

    Show the answer: Question 8 of 11

    Answer: Loop with for mark in marks and add each mark

    Looping over the array itself, for mark in marks, removes the index and every off-by-one mistake with it. When an index is needed, 0..marks.len() stops before the length.

  9. Question 9 of 11 let total: u64 = count; where count is a u32. Does it compile?

    Choose one answer.

    Show the answer: Question 9 of 11

    Answer: No, it is E0308, mismatched types, and rustc suggests count.into()

    Every conversion between number types is written out. Widening ones use From or into, which cannot lose information.

  10. Question 10 of 11 What does 300i32 as u8 give?

    Type a number.

    Show the answer: Question 10 of 11

    Answer: 44

    as never fails, so 300 becomes some u8: 44, as conversions.rs printed. u8::try_from(300i32) would have returned an error instead.

  11. Question 11 of 11 A count arrives as an i64 from a file and must become a u8 for a display that shows 0 to 255. Which conversion fits?

    Choose one answer.

    Show the answer: Question 11 of 11

    Answer: u8::try_from(count), and handle the error

    A value from outside the program can be anything, so the conversion must check it: TryFrom returns an error for values that do not fit, and the program decides what to show.

Interview questions

Warm-up (fresher): what is the difference between a tuple and an array in Rust?

Both have a fixed length that is part of their type. A tuple groups a few values that may each have their own type, such as (&str, u32), reached by positions written in the code, .0 and .1, or by destructuring. An array holds values of one type, [u32; 4], so it can be indexed with a position worked out while the program runs and looped over, with every index checked. A tuple suits a few values that belong together, such as two results of a function; an array, or a Vec if the length changes, suits many values of one kind.

  1. Fresher to mid

    An upload service stores each file's size, a u64 from the file system, in a column it fills with size as i32. For one 3 GiB file the dashboard shows a negative size, and the quota check let the upload through. What happened, and what do you change?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to senior

    An HTTP handler parses ?slot= from a request into an i64 and returns slots[slot as usize] from an array of 48 time slots. A crafted request makes the handler panic. What happened, and how would you write the lookup?

    Model answer, Pro. First step and follow-ups

  3. Fresher to mid

    A function returns (u32, u32, bool): the bytes read, the lines read and whether the input ended early. After a refactor, a caller swaps the two u32 values and nobody notices for a week. What would you change, and when is a tuple still the right return type?

    Model answer, Pro. First step and follow-ups

Key takeaways

  • Rust tuples group a fixed number of values of any types, read with .0 and .1 or by destructuring, and () is the empty tuple.
  • An array’s length is part of its type; get returns None past the end, and a [] index out of range is refused when rustc can see it and panics when it cannot.
  • Number types never convert by themselves; of the three type conversions, as never fails but can lose information, From exists only where nothing is lost, and TryFrom checks.

Terms in this lesson

bounds check
The check that an index is inside a list before the value there is read. In safe Rust a failed check stops the program with a panic, so nothing past the end is ever read.
tuple
A value that groups a fixed number of values, each of its own type, such as (&str, u32). Its values are read by position, .0 and .1, or taken apart all at once with a pattern.
unit type
The type of the empty tuple, written (), with exactly one value, also written (). It takes no memory, and it is what a function returns when it returns nothing.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.