Rust Basic
Built-in Types
Scalar Types
A scalar type represents a single value.
Types | Literals | |
---|---|---|
Signed integers | i8 , i16 , i32 , i64 , i128 , isize |
-10 , 0 , 1_000 , 123_i64 |
Unsigned integers | u8 , u16 , u32 , u64 , u128 , usize |
0 , 123 , 10_u16 |
Floating point numbers | f32 , f64 |
3.14 , -10.0e20 , 2_f32 |
Unicode scalar values | char |
'a' , 'α' , '∞' |
Booleans | bool |
true , false |
The types have widths as follows:
iN
,uN
, andfN
are N bits wide,isize
andusize
are the width of a pointer,char
is 32 bits wide,bool
is 8 bits wide.
Additionally, the isize
and usize
types depend on the architecture of the computer your program is running on, which is denoted in the table as "arch": 64 bits if you’re on a 64-bit architecture and 32 bits if you’re on a 32-bit architecture.
Some people will be confused by machine's architecture bit size, which refers to the memory addressing length(the length of pointers). You can directly use 64-bits int in a 32-bits machine, with minor discount in performance. The compiler may need to generate several machine code instructions to perform operations on the 64 bit values, slowing down those operations by several times.
For float point number, rust implement related literal like INFINITY
, NEG_INFINITY
, NAN
, MIN
, MAX
Compound Types
- Tuple
- Array/Vec/Slice
- Struct
- Enum
- Pointer
- Reference
- Box
- Bare Pointer
- String
Ownership
- The concept of copying the pointer, length, and capacity without copying the data probably sounds like making a shallow copy. But because Rust also invalidates the first variable, instead of being called a shallow copy, it’s known as a move
- Followed by an important trait
Copy
. If a type implements theCopy
trait, variables that use it do not move, but rather are trivially copied, making them still valid after assignment to another variable. Here are some of the types that implementCopy
:- All the integer types, such as
u32
. - The Boolean type,
bool
, with valuestrue
andfalse
. - All the floating-point types, such as
f64
. - The character type,
char
. - Tuples, if they only contain types that also implement
Copy
. For example,(i32, i32)
implementsCopy
, but(i32, String)
does not.
- All the integer types, such as
Rc
andArc
can share the ownership of the variable,A
means support withatomic
. It's clear thatRc
provides faster counter update, whileArc
gives us more security update guarantee.
Struct
- Everything within this
impl
block will be associated with specific type. - We call
rect1.area()
is a method syntax callingarea
method onRectangle
instance.
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main () {
let rect1 = {
width: 10,
height: 20,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
Rust doesn’t have an equivalent to the
->
operator; instead, Rust has a feature called automatic referencing and dereferencing. Calling methods is one of the few places in Rust that has this behavior.
- Here’s how it works: when you call a method with
object.something()
, Rust automatically adds in&
,&mut
, or*
soobject
matches the signature of the method. In other words, the following are the same:p1.distance(&p2); (&p1).distance(&p2);
The first one looks much cleaner. This automatic referencing behavior works because methods have a clear receiver—the type ofself
. Given the receiver and name of a method, Rust can figure out definitively whether the method is reading (&self
), mutating (&mut self
), or consuming (self
). The fact that Rust makes borrowing implicit for method receivers is a big part of making ownership ergonomic in practice.
Enum
- We call
V4
andV6
the variants of the enum
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
let loopback = IpAddr {
kind: IpAddrKind::V6,
address: String::from1",
};
- A more consise implementation here
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4from("127.0.0.1");
let loopback = IpAddr::V61");
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V61");
- There is one more similarity between enums and structs: just as we’re able to define methods on structs using
impl
, we’re also able to define methods on enums. Here’s a method namedcall
that we could define on ourMessage
enum
impl Message {
fn call(&self) {
// method body would be defined here
}
}
let m = Message::Writefrom("hello");
m.call();
The Option
Enum and Its Advantages Over Null Values
Programming language design is often thought of in terms of which features you include, but the features you exclude are important too. Rust doesn’t have the null feature that many other languages have. Null is a value that means there is no value there. In languages with null, variables can always be in one of two states: null or not-null.
- “Null References---The Billion Dollar Mistake”, Tony Hoare
The problem with null values is that if you try to use a null value as a not-null value, you’ll get an error of some kind. Because this null or not-null property is pervasive, it’s extremely easy to make this kind of error.
However, the concept that null is trying to express is still a useful one: a null is a value that is currently invalid or absent for some reason.
The problem isn’t really with the concept but with the particular implementation. As such, Rust does not have nulls, but it does have an enum that can encode the concept of a value being present or absent. This enum is Option<T>
, and it is defined by the standard library as follows:
enum Option<T> {
None,
Some(T),
}
let some_number = Some(5);
let some_char = Some('e');
let absent_number: Option<i32> = None;
The match
Control Flow Construct
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn reroll() {}
Concise Control Flow with if let
let mut count = 0;
match coin {
Coin::Quarter(state) => println!("State quarter from {state:?}!"),
_ => count += 1,
}
let mut count = 0;
if let Coin::Quarter(state) = coin {
println!("State quarter from {state:?}!");
} else {
count += 1;
}