Hello! Welcome back to our exploration of Rust for Solana development.
In the last lesson, we focused on how to declare variables using let, let mut, and shadowing. We learned that Rust prioritizes safety by making variables immutable by default. Now that you know how to create variables, our next step is to understand the different kinds of data they can hold.
Today's lesson addresses a fundamental aspect of Rust: its data types. Every value in Rust has a specific type, which the compiler must know. This is a core feature that helps ensure your programs are correct and memory-safe—a non-negotiable requirement for building robust smart contracts. We will distinguish between two main categories: scalar types, which represent a single value, and compound types, which group multiple values together.
A Quick Tour of Rust's Data Types
Before we dive into the details, let's get a high-level overview. Rust's data types can feel extensive at first, but they follow a clear and logical structure. The following video provides a concise tour of the types we'll be covering today.
This video from the Rustfully channel gives a quick introduction to scalar types (integers, floats, booleans, characters) and compound types (tuples, arrays), which is a perfect roadmap for our lesson.
Watch the entire video (about 4 minutes). It will introduce you to the main concepts and terminology we'll be exploring in more detail.
Scalar Types: The Building Blocks
As the video mentioned, a scalar type represents a single value. Rust has four primary scalar types, which will likely feel familiar from your experience with other languages, though with some important distinctions.
- Integers: Numbers without a fractional component.
- Floating-Point Numbers: Numbers with a decimal point.
- Booleans:
trueorfalsevalues. - Characters: A single Unicode character.
For a detailed look at each of these, the official Rust book is the best resource.
Data Types - The Rust Programming Language
This chapter from 'The Rust Programming Language' provides the definitive explanation of Rust's primitive data types. We'll use it to explore each scalar type in detail.
Please read the following sections from the webpage: Scalar Types: Start here for the main definition. Integer Types: Pay close attention to the table of integer types (e.g., u8, i32). The distinction between signed (i) and unsigned (u) is crucial. In Solana, you will frequently work with specific unsigned integers like u64 for SOL balances (lamports) and u8 for bytes of data. Floating-Point Types: Read this section to understand f32 and f64. The Boolean Type: A quick read on the bool type. The Character Type: Note that char uses single quotes and represents a Unicode value, which is different from a string. You can skip the 'Numeric Operations' section for now, as we'll see those in practice later.
A key takeaway here is Rust's specificity. In JavaScript, you have number for all numeric values. In Rust, you must choose the exact type of integer (i32, u64, etc.). This isn't just for ceremony; it allows the compiler to make precise memory allocations and prevent errors like integer overflow, where a number exceeds the capacity of its type. For example, a u8 can only hold values from 0 to 255. Trying to assign 256 to it will cause an error in debug mode.
Test your understanding!
You are writing a Solana program where you need to store the number of tokens a user owns. This number can never be negative, and for this specific token, it will not exceed 1,000,000. Which of the following integer types is the most appropriate and efficient choice?
A) i32
B) u8
C) u32
D) f64
Show answer
The correct answer is C) u32.
ufor Unsigned: Since the number of tokens cannot be negative, an unsigned integer (u) is the correct choice. This rules outi32.- Size Matters: A
u8can only store values up to 255, which is too small. Au32can store values up to 4,294,967,295, which is large enough to hold 1,000,000. While au64would also work,u32is more memory-efficient since it uses half the space. - No Decimals:
f64is a floating-point type, which is incorrect for representing a whole number of tokens.
Compound Types: Grouping Values
Compound types group multiple values into one type. Rust has two primitive compound types: tuples and arrays. Their main distinction lies in the types of elements they can hold.
Coming from JavaScript, this is an area with some important differences. JavaScript arrays are highly flexible collections. In Rust, you have more specialized tools for different needs.
The Tuple Type
A tuple is a fixed-length collection of values that can have different types. They are perfect for grouping a small, fixed number of related but distinct pieces of information. For example, returning an IP address and port number from a function.
The Array Type
An array, like a tuple, also has a fixed length. However, every element in an array must have the same type. This homogeneity makes arrays predictable and efficient.
Once again, let's turn to the Rust book for a thorough explanation.
Data Types - The Rust Programming Language
Continuing in the same chapter, we will now cover Rust's primitive compound types.
Please read the following sections: Compound Types: The brief introduction to the category. The Tuple Type: Focus on how to create a tuple, how to destructure it to get values out, and how to access elements directly with a period (e.g., tup.0). The Array Type: Pay attention to the syntax for an array's type annotation ([type; size]). Also, note how to access elements with square brackets (e.g., a[0]). Take note of the core difference: tuples are heterogeneous (different types), while arrays are homogeneous (same type).
In the context of Solana, you will see arrays everywhere. A user's public key, a program's ID, and transaction hashes are all typically represented as 32-byte arrays: [u8; 32]. Their fixed size and stack allocation make them very efficient for the high-performance needs of the blockchain.
Unlike JavaScript arrays, Rust arrays cannot grow or shrink. For a dynamic, growable list, Rust provides a Vector type, which we will cover in a future lesson. For now, it's crucial to remember that a Rust array has a size that is fixed at compile time.
Test your understanding!
You are tasked with defining a variable to hold the GPS coordinates of a location. A coordinate consists of a latitude and a longitude, both of which are floating-point numbers. Which data structure should you use and why?
A) An array [f64; 2]
B) A tuple (f64, f64)
Choose your answer and explain the trade-offs, if any.
Show answer
While both A and B are technically possible since both values are f64, the most idiomatic and clear choice is B) a tuple (f64, f64).
Here's why:
- A tuple is designed to group together a set of values with different meanings, even if they happen to have the same type. Here, the first element is "latitude" and the second is "longitude." Using a tuple
(latitude, longitude)preserves this semantic distinction. - An array is meant for a sequence of interchangeable elements of the same type. While latitude and longitude are both floats, they are not interchangeable; swapping them changes the location.
- Using a tuple makes your intent clearer. When another developer sees
(f64, f64), they are more likely to think "a pair of related but distinct values" than when they see[f64; 2], which suggests "a list of two floats."
Conclusion
Great work! You've now covered the primitive data types that form the foundation of all Rust programs. Understanding these types and their constraints is the first major step toward leveraging Rust's compiler to write safe and efficient code.
Here are the key takeaways from today's lesson:
- Scalar types represent a single value:
- Integers (
u8,i32,u64, etc.) for whole numbers. - Floats (
f32,f64) for decimal numbers. - Booleans (
bool) fortrue/false. - Characters (
char) for single Unicode characters.
- Integers (
- Compound types group multiple values:
- Tuples have a fixed size and can contain elements of different types.
- Arrays have a fixed size and must contain elements of the same type.
- This strict type system is a core feature of Rust that prevents bugs and is essential for secure smart contract development on Solana.
In our next lesson, we will see these data types in action as we learn how to define and call functions, passing data in as parameters and getting values back as return types.
Can't find a good explanation? Sign up and we'll make it for you
Sign up