mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-04 22:00:34 +08:00
234 lines
10 KiB
Markdown
234 lines
10 KiB
Markdown
|
[#]: subject: "Rust Basics Series #3: Data Types in Rust"
|
||
|
[#]: via: "https://itsfoss.com/rust-data-types/"
|
||
|
[#]: author: "Pratham Patel https://itsfoss.com/author/pratham/"
|
||
|
[#]: collector: "lkxed"
|
||
|
[#]: translator: " "
|
||
|
[#]: reviewer: " "
|
||
|
[#]: publisher: " "
|
||
|
[#]: url: " "
|
||
|
|
||
|
Rust Basics Series #3: Data Types in Rust
|
||
|
======
|
||
|
|
||
|
![][1]
|
||
|
|
||
|
In the [previous post][2] about the Rust programming language, we looked at variables, constants and shadowing.
|
||
|
|
||
|
It is only natural to cover data types now.
|
||
|
|
||
|
### What are data types?
|
||
|
|
||
|
Change the order of these words and you get your answer; "data types" -> "type of data".
|
||
|
|
||
|
The computer stores data as `0`s and `1`s but to make sense of it when reading, we use data type to say what those `0`s and `1`s mean.
|
||
|
|
||
|
Rust has two types of data types:
|
||
|
|
||
|
- **Scalar data type**: Types that store only a single value.
|
||
|
- **Compound data type**: Types that store multiple values, even values of different types.
|
||
|
|
||
|
In this article, I shall cover scalar data types. I will go through the second category in the next article.
|
||
|
|
||
|
Following is a brief overview of the four main categories of Scalar data types in Rust:
|
||
|
|
||
|
- **Integers**: Stores whole numbers. Has sub-types for each specific use case.
|
||
|
- **Floats**: Stores numbers with a fractional value. Has two sub-types based on size.
|
||
|
- **Characters**: Stores a single character of UTF-8 encoding. (Yes, you can store an emoji* in a character.)
|
||
|
- **Booleans**: Stores either a `true` or a `false`. (For developers who can't agree if `0` is `true` or if `0` means `false`.)
|
||
|
|
||
|
### Integers
|
||
|
|
||
|
An integer in the context of a programming language refers to whole numbers. Integers in Rust are either **Signed** or **Unsigned**. Unsigned integers store only 0 and positive numbers, while Signed integers can store negative numbers, 0 and positive numbers.
|
||
|
|
||
|
> 💡 The range of Signed integers begins from `-(2n-1)` and this range ends with `(2n-1)-1`. Likewise, the range for Unsigned integers starts at `0` and ends with `(2n)-1`.
|
||
|
|
||
|
Following are the available Integer types based on the sign and length:
|
||
|
|
||
|
![Integer data types in Rust][3]
|
||
|
|
||
|
As you can see, Rust has Signed and Unsigned integers of length 8, 16, 32, 64 and even 128!
|
||
|
|
||
|
The integers with `*size` vary based on the architecture of the computer. On 8-bit micro-controllers, it is `*8`, on 32-bit legacy computers, it is `*32` and on modern 64-bit systems, it is `*64`.
|
||
|
|
||
|
The use of `*size` is to store data that is mostly related to memory (which is machine dependent), like pointers, offsets, etc.
|
||
|
|
||
|
> 💡 When you do not explicitly specify a subset of the Integer type, the Rust compiler will infer it's type to be `i32` by default. Obviously, if the value is bigger or smaller than what `i32` can hold, the Rust compiler will politely error out and ask you to manually annotate the type.
|
||
|
|
||
|
Rust not only allows you to store integers in their decimal form but also in the binary, octal and hex forms too.
|
||
|
|
||
|
For better readability, you can use underscore `_` as a replacement for commas in writing/reading big numbers.
|
||
|
|
||
|
```
|
||
|
fn main() {
|
||
|
let bin_value = 0b100_0101; // use prefix '0b' for Binary representation
|
||
|
let oct_value = 0o105; // use prefix '0o' for Octals
|
||
|
let hex_value = 0x45; // use prefix '0x' for Hexadecimals
|
||
|
let dec_value = 1_00_00_000; // same as writing 1 Crore (1,00,00,000)
|
||
|
|
||
|
println!("bin_value: {bin_value}");
|
||
|
println!("oct_value: {oct_value}");
|
||
|
println!("hex_value: {hex_value}");
|
||
|
println!("dec_value: {dec_value}");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
I have stored the decimal number 69 in binary form, octal form and hexadecimal form in the variables `bin_value`, `oct_value` and `hex_value` respectively. In the variable `dec_value`, I have stored the number [1 Crore][4] (10 million) and have commas with underscores, as per the Indian numbering system. For those more familiar with the International numbering system, you may write this as `10_000_000`.
|
||
|
|
||
|
Upon compiling and running this binary, I get the following output:
|
||
|
|
||
|
```
|
||
|
bin_value: 69
|
||
|
oct_value: 69
|
||
|
hex_value: 69
|
||
|
dec_value: 10000000
|
||
|
```
|
||
|
|
||
|
### Floating point numbers
|
||
|
|
||
|
Floating point numbers, or more commonly known as "float(s)" is a data type that holds numbers that have a fractional value (something after the decimal point).
|
||
|
|
||
|
Unlike the Integer type in Rust, Floating point numbers have only two subset types:
|
||
|
|
||
|
- `f32`: Single precision floating point type
|
||
|
- `f64`: Double precision floating point type
|
||
|
|
||
|
Like the Integer type in Rust, when Rust infers the type of a variable that seems like a float, it is assigned the `f64` type. This is because the `f64` type has more precision than the `f32` type and is almost as fast as the `f32` type in most computational operations. Please note that _both the floating point data types (`f32` and `f64`) are **Signed**_.
|
||
|
|
||
|
> 📋 The Rust programming language stores the floating point numbers as per the [IEEE 754][5] standard of floating point number representation and arithmetic.
|
||
|
|
||
|
```
|
||
|
fn main() {
|
||
|
let pi: f32 = 3.1400; // f32
|
||
|
let golden_ratio = 1.610000; // f64
|
||
|
let five = 5.00; // decimal point indicates that it must be inferred as a float
|
||
|
let six: f64 = 6.; // even the though type is annotated, a decimal point is still
|
||
|
// **necessary**
|
||
|
|
||
|
println!("pi: {pi}");
|
||
|
println!("golden_ratio: {golden_ratio}");
|
||
|
println!("five: {five}");
|
||
|
println!("six: {six}");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
Look closely at the 5th line. Even though I have annotated the type for the variable `six`, I **need** to at least use the decimal point. If you have something _after_ the decimal point is up to you.
|
||
|
|
||
|
The output of this program is pretty predictable... Or is it?
|
||
|
|
||
|
```
|
||
|
pi: 3.14
|
||
|
golden_ratio: 1.61
|
||
|
five: 5
|
||
|
six: 6
|
||
|
```
|
||
|
|
||
|
In the above output, you might have noticed that while displaying the value stored inside variables `pi`, `golden_ratio` and `five`, the trailing zeros that I specified at the time of variable declaration, are missing.
|
||
|
|
||
|
While those zeros are not _removed_, they are omitted while outputting the values via the `println` macro. So no, Rust did not tamper with your variable's values.
|
||
|
|
||
|
### Characters
|
||
|
|
||
|
You can store a single character in a variable and the type is simply `char`. Like traditional programming languages of the '80s, you can store an [ASCII][6] character. But Rust also extends the character type to store a valid UTF-8 character. This means that you can store an emoji in a single character 😉
|
||
|
|
||
|
> 💡 Some emojis are a mix of two existing emojis. A good example is the 'Fiery Heart' emoji: ❤️🔥. This emoji is constructed by combining two emojis using a [zero width joiner][7]: ❤️ + 🔥 = ❤️🔥
|
||
|
>
|
||
|
> Storing such emojis in a single Rust variable of the character type is not possible.
|
||
|
|
||
|
```
|
||
|
fn main() {
|
||
|
let a = 'a';
|
||
|
let p: char = 'p'; // with explicit type annotation
|
||
|
let crab = '🦀';
|
||
|
|
||
|
println!("Oh look, {} {}! :{}", a, crab, p);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
As you can see, I have stored the ASCII characters 'a' and 'p' inside variables `a` and `p`. I also store a valid UTF-8 character, the crab emoji, in the variable `crab`. I then print the characters stored in each of these variables.
|
||
|
|
||
|
Following is the output:
|
||
|
|
||
|
```
|
||
|
Oh look, a 🦀! :p
|
||
|
```
|
||
|
|
||
|
### Booleans
|
||
|
|
||
|
The boolean type in Rust stores only one of two possible values: either `true` or `false`. If you wish to annotate the type, use `bool` to indicate the type.
|
||
|
|
||
|
```
|
||
|
fn main() {
|
||
|
let val_t: bool = true;
|
||
|
let val_f = false;
|
||
|
|
||
|
println!("val_t: {val_t}");
|
||
|
println!("val_f: {val_f}");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
The above code, when compiled and executed results in the following output:
|
||
|
|
||
|
```
|
||
|
val_t: true
|
||
|
val_f: false
|
||
|
```
|
||
|
|
||
|
### Bonus: Explicit typecasting
|
||
|
|
||
|
In the previous article about Variables in the Rust programming language, I showed a very basic [temperature conversion program][8]. In there, I mentioned that Rust does not allow implicit typecasting.
|
||
|
|
||
|
But that doesn't mean that Rust does not allow _explicit_ typecasting either ;)
|
||
|
|
||
|
To perform explicit type casting, the `as` keyword is used and followed by the data type to which the value should be cast in.
|
||
|
|
||
|
Following is a demo program:
|
||
|
|
||
|
```
|
||
|
fn main() {
|
||
|
let a = 3 as f64; // f64
|
||
|
let b = 3.14159265359 as i32; // i32
|
||
|
|
||
|
println!("a: {a}");
|
||
|
println!("b: {b}");
|
||
|
}
|
||
|
```
|
||
|
|
||
|
On line 2, instead of using '3.0', I follow the '3' with `as f64` to denote that I want the compiler to handle type casting of '3' (an Integer) into a 64-bit float. Same with the 3rd line. But here, the type casting is **lossy**. Meaning, that the fractional element is _completely gone_. Instead of storing `3.14159265359`, it is stored as simply `3`.
|
||
|
|
||
|
This can be verified from the program's output:
|
||
|
|
||
|
```
|
||
|
a: 3
|
||
|
b: 3
|
||
|
```
|
||
|
|
||
|
### Conclusion
|
||
|
|
||
|
This article covers the Primitive/Scalar data types in Rust. There are primarily four such data types: Integers, Floating point numbers, Characters and Booleans.
|
||
|
|
||
|
Integers are used to store whole numbers and they have several sub-types based on either they are signed or unsigned and the length. Floating point numbers are used to store numbers with some fractional values and have two sub-types based on length. The character data type is used to store a single, valid UTF-8 encoded character. Finally, booleans are used to store either a `true` or `false` value.
|
||
|
|
||
|
In the next chapter, I'll discuss compound data types like arrays and tuples. Stay tuned.
|
||
|
|
||
|
--------------------------------------------------------------------------------
|
||
|
|
||
|
via: https://itsfoss.com/rust-data-types/
|
||
|
|
||
|
作者:[Pratham Patel][a]
|
||
|
选题:[lkxed][b]
|
||
|
译者:[译者ID](https://github.com/译者ID)
|
||
|
校对:[校对者ID](https://github.com/校对者ID)
|
||
|
|
||
|
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||
|
|
||
|
[a]: https://itsfoss.com/author/pratham/
|
||
|
[b]: https://github.com/lkxed/
|
||
|
[1]: https://itsfoss.com/content/images/2023/04/humble-bundle-packt-offer.webp
|
||
|
[2]: https://itsfoss.com/rust-variables
|
||
|
[3]: https://media.discordapp.net/attachments/1001332168506753024/1062952929734164540/data-type-table1.png
|
||
|
[4]: https://en.wikipedia.org/wiki/Crore?ref=itsfoss.com
|
||
|
[5]: https://en.wikipedia.org/wiki/IEEE_754?ref=itsfoss.com
|
||
|
[6]: https://www.ascii-code.com/?ref=itsfoss.com
|
||
|
[7]: https://unicode-table.com/en/200D/?ref=itsfoss.com
|
||
|
[8]: https://itsfoss.com/rust-variables/#a-rusty-thermometer
|