We use an mdbook preprocessor to automatically generate links to the relevant exercise for each section. We remove all existing manual links and refactor the deploy process to push the rendered book to a branch.
5.2 KiB
Types, part 1
In the “Syntax” section
compute’s input parameters were of type
u32.
Let’s unpack what that means.
Primitive types
u32 is one of Rust’s primitive types.
Primitive types are the most basic building blocks of a language.
They’re built into the language itself—i.e. they are not defined in
terms of other types.
You can combine these primitive types to create more complex types. We’ll see how soon enough.
Integers
u32, in particular, is an unsigned 32-bit
integer.
An integer is a number that can be written without a fractional
component. E.g. 1 is an integer, while 1.2 is
not.
Signed vs. unsigned
An integer can be signed or
unsigned.
An unsigned integer can only represent non-negative numbers
(i.e. 0 or greater). A signed integer can represent both
positive and negative numbers (e.g. -1, 12,
etc.).
The u in u32 stands for
unsigned.
The equivalent type for signed integer is i32, where the
i stands for integer (i.e. any integer, positive or
negative).
Bit width
The 32 in u32 refers to the number
of bits1 used to represent the
number in memory.
The more bits, the larger the range of numbers that can be
represented.
Rust supports multiple bit widths for integers: 8,
16, 32, 64, 128.
With 32 bits, u32 can represent numbers from
0 to 2^32 - 1 (a.k.a. u32::MAX).
With the same number of bits, a signed integer (i32) can
represent numbers from -2^31 to 2^31 - 1
(i.e. from i32::MIN
to i32::MAX).
The maximum value for i32 is smaller than the maximum value
for u32 because one bit is used to represent the sign of
the number. Check out the two’s
complement representation for more details on how signed integers
are represented in memory.
Summary
Combining the two variables (signed/unsigned and bit width), we get the following integer types:
| Bit width | Signed | Unsigned |
|---|---|---|
| 8-bit | i8 |
u8 |
| 16-bit | i16 |
u16 |
| 32-bit | i32 |
u32 |
| 64-bit | i64 |
u64 |
| 128-bit | i128 |
u128 |
Literals
A literal is a notation for representing a fixed
value in source code.
For example, 42 is a Rust literal for the number
forty-two.
Type annotations for literals
But all values in Rust have a type, so… what’s the type of
42?
The Rust compiler will try to infer the type of a literal based on
how it’s used.
If you don’t provide any context, the compiler will default to
i32 for integer literals.
If you want to use a different type, you can add the desired integer
type as a suffix—e.g. 2u64 is a 2 that’s explicitly typed
as a u64.
Underscores in literals
You can use underscores _ to improve the readability of
large numbers.
For example, 1_000_000 is the same as
1000000.
Arithmetic operators
Rust supports the following arithmetic operators2 for integers:
+for addition-for subtraction*for multiplication/for division%for remainder
Precedence and associativity rules for these operators are the same
as in mathematics.
You can use parentheses to override the default precedence. E.g.
2 * (3 + 4).
⚠️ Warning
The division operator
/performs integer division when used with integer types. I.e. the result is truncated towards zero. For example,5 / 2is2, not2.5.
No automatic type coercion
As we discussed in the previous exercise, Rust is a statically typed
language.
In particular, Rust is quite strict about type coercion. It won’t
automatically convert a value from one type to another3,
even if the conversion is lossless. You have to do it explicitly.
For example, you can’t assign a u8 value to a variable
with type u32, even though all u8 values are
valid u32 values:
let b: u8 = 100;
let a: u32 = b;It’ll throw a compilation error:
error[E0308]: mismatched types
|
3 | let a: u32 = b;
| --- ^ expected `u32`, found `u8`
| |
| expected due to this
|
We’ll see how to convert between types later in this course.
Further reading
- The integer types section in the official Rust book
A bit is the smallest unit of data in a computer. It can only have two values:
0or1.↩︎Rust doesn’t let you define custom operators, but it puts you in control of how the built-in operators behave. We’ll talk about operator overloading later in the course, after we’ve covered traits.↩︎
There are some exceptions to this rule, mostly related to references, smart pointers and ergonomics. We’ll cover those later on. A mental model of “all conversions are explicit” will serve you well in the meantime.↩︎