What a data type is
Every value in a Rust program has a data type that says what kind of data it is and what you may do with it. The compiler knows the type of every value before the program runs, and it uses that knowledge for two jobs: choosing what an operation means, and refusing operations that make no sense.
These are the basic types you will use first:
| Type | Holds | Example values |
|---|---|---|
i32 | whole numbers | 3, -12 |
f64 | numbers with a fractional part | 3.14, -0.5 |
&str | borrowed text | "John Doe" |
String | owned, growable text | String::from("John") |
bool | true or false | true, false |
char | one character | 'a', 'Z', '?' |
The same symbol, different meanings
The expression a + b can mean different things depending on the types of a and b. With two numbers it adds them:
let a: i32 = 10;
let b: i32 = -5;
println!("{}", a + b); // Output: 5
With a String on the left and a &str on the right it joins text:
let a = String::from("Hello ");
let b = "world!";
println!("{}", a + b); // Output: Hello world!
Addition and concatenation are different operations, and the compiler picks the right one from the types. The choice is strict, though. Two &str values can't be added, and the pair of types has to be one the language defines.
Invalid operations are compile errors
Knowing the types also lets Rust catch nonsense before the program runs. Multiplying two strings is not defined:
let a = "peanut";
let b = "butter";
println!("{}", a * b);
The compiler rejects this with error[E0369]: cannot multiply `&str` by `&str`. Nothing is built, so there is no run-time failure to chase. The same happens when you mix number types. Adding an i32 to an f64 gives error[E0277]: cannot add `f64` to `i32`, because Rust never converts between number types silently. Convert one side yourself when you need to.
Types the compiler checks in declarations
A type annotation, written after the variable name, states the type you intend. If the value doesn't fit, you get mismatched types:
let b: bool = 1;fails withexpected `bool`, found integer. Rust has no "truthy" numbers.let c: char = "a";fails withexpected `char`, found `&str`. Acharuses single quotes, text uses double quotes.let s: String = "Hello";fails as well: a literal is a&str, andString::from("Hello")is needed to make an ownedString.
Numbers: integer or float
Integers and floats are separate types, so the same-looking operation behaves differently. Integer division discards the fractional part: 5 / 2 is 2. Float division keeps it: 5.0 / 2.0 is 2.5. A float with no fractional part, such as 7.0, prints as 7.
The 32 in i32 is the number of bits it uses, which fixes its range from -2147483648 to 2147483647. Rust has other sizes too, such as i64, but i32 is the default for a plain integer literal and f64 for a plain decimal literal.
Text: &str, String and char
Rust has two main string types. A literal in double quotes is a &str: it borrows text that is fixed in the program and can't be changed or grown. A String owns its text, so it can be modified and passed around as its own value. You turn a literal into a String with String::from, or with .to_string().
A char is a single character in single quotes. It isn't limited to ASCII, because it holds any Unicode character, and 'a' and "a" are different types: one is a char, the other a &str of length one.
No null: Option
Rust has no null. When a value might be missing, its type says so. Option<i32> is either Some(10), holding a value, or None:
let maybe_age: Option<i32> = Some(10);
let nothing: Option<i32> = None;
println!("{:?} {:?}", maybe_age, nothing); // Output: Some(10) None
An Option<i32> is not an i32, so maybe_age + 1 doesn't compile. You must handle the missing case before using the number. That is how the type system prevents a whole family of bugs.
Solving the exercise
You declare four variables, each with an explicit type and a matching value:
my_intasi32, with a whole number such as5.my_floatasf64, with a decimal literal such as3.14. A value like3would be an integer and be rejected.my_stringasString, created withString::from("Hello")so the variable owns its text.my_boolasbool, withtrueorfalse.
let my_int: i32 = 5;
let my_float: f64 = 3.14;
let my_string: String = String::from("Hello");
let my_bool: bool = true;
Printing them with println!("{} {} {} {}", my_int, my_float, my_string, my_bool) gives 5 3.14 Hello true.
Key takeaways
- Every value has a type, and Rust checks types when it compiles, before the program runs.
- The core types are
i32,f64,&strandString,boolandchar. - Integers and floats are different types and don't mix without a conversion.
- Text in double quotes is a
&str, a single character in single quotes is achar, andString::frommakes an ownedString. - A value that may be absent is an
Option, notnull.
