变量
变量声明
局部变量
局部变量声明一定是以关键字let开头,类型一定是跟在冒号:的后面。
let x = 5; // 不可变变量
let mut y = 10; // 可变变量
let
关键字用于声明变量。mut
关键字表示变量是可变的。
常量
const MAX_POINTS: u32 = 100_000;
- 常量用
const
声明,必须指定类型,且在整个程序运行过程中保持不变。
静态变量
- 使用
static
关键字来声明静态变量。 - 静态变量在整个程序运行期间存在,拥有
static
生命周期(整个程序。 - 静态变量的内存分配在编译时完成(不会在执行过程中收回。
- 默认情况下,静态变量是不可变的;可以使用
static mut
声明可变静态变量,但需要使 用unsafe
代码块来访问和修改。 - ·全局变量必须在声明的时候马上初始化;
- 全局变量的初始化必须是编译期可确定的常量,不能包括执行期才能确定的表达式、语句和函数调用;
不可变静态变量
-
声明和使用不可变静态变量
static HELLO_WORLD: &str = "Hello, world!";
fn main() {
println!("{}", HELLO_WORLD);
}
可变静态变量
-
声明和使用可变静态变量
static mut COUNTER: i32 = 0;
fn add_to_counter(value: i32) {
unsafe {
COUNTER += value;
}
}
fn main() {
add_to_counter(3);
unsafe {
println!("COUNTER: {}", COUNTER);
}
}
线程安全的可变静态变量
-
使用
Mutex
确保线程安全use std::sync::Mutex;
static GLOBAL_COUNTER: Mutex<i32> = Mutex::new(0);
fn main() {
{
let mut counter = GLOBAL_COUNTER.lock().unwrap();
*counter += 1;
}
println!("GLOBAL_COUNTER: {}", GLOBAL_COUNTER.lock().unwrap());
}
类型推导
- Rust 可以自动推导变量的类型,不需要显式指定 类型,除非编译器无法推导出。
类型推导示例
-
简单变量的类型推导
let x = 5; // x 被推导为 i32
let y = 3.14; // y 被推导为 f64
let is_active = true; // is_active 被推导为 bool
let name = "Alice"; // name 被推导为 &str -
推导数组类型
let numbers = [1, 2, 3, 4, 5]; // numbers 被推导为 [i32; 5]
显式指定类型的情况
-
当类型不明确或无法推导时需要显式指定
let guess: u32 = "42".parse().expect("Not a number!");
-
使用泛型或复杂类型时
let result: Result<i32, _> = "42".parse();
初始化绑定 Initializing bindings
-
在声明变量时立即为其赋值。
-
无构造函数,无初始值
如果不赋值,Rust 是不会让我们使用一个没有经过初始化的值的。
举例:
fn main() {
let x: i32;
println!("The value of x is: {}", x);
}
构建后出现,error
use of possibly uninitialized variable: `x`
作用域 Scope
- 变量在程序中有效的范围。
- 通过花括号
{}
来定义作用域。 - 变量超出作用域时会被自动销毁。
示例:
-
变量的作用域
fn main() {
let x = 5; // x 的作用域开始
{
let y = 10; // y 的作用域开始
println!("x: {}, y: {}", x, y);
} // y 的作用域结束
// println!("y: {}", y); // 编译错误:y 不在作用域内
println!("x: {}", x);
} // x 的作用域结束 -
嵌套作用域
fn main() {
let x = 5;
{
let x = x * 2;
println!("Inner scope x: {}", x); // 打印: Inner scope x: 10
}
println!("Outer scope x: {}", x); // 打印: Outer scope x: 5
}
隐藏 shadowing
- 在同一作用域或内嵌作用域中重新声明一个变量名,以隐藏前一个同名变量。
- 隐藏允许改变变量的类型和可变性。
示例
-
简单的隐藏
fn main() {
let x = 5;
let x = x + 1; // 隐藏了之前的 x
{
let x = x * 2; // 在嵌套作用域中再次隐藏 x
println!("Inner scope x: {}", x); // 打印: Inner scope x: 12
}
println!("Outer scope x: {}", x); // 打印: Outer scope x: 6
} -
改变变量类型和可变性
fn main() {
let spaces = " ";
let spaces = spaces.len(); // 隐藏并改变类型
println!("spaces length: {}", spaces);
let mut x = 5; // 可变变量
x = 6;
let x = x; // 隐藏并变为不可变变量
// x = 7; // 编译错误:x 现在是不可变的
}
数据类型
基本数据类型
bool
Rust 的布尔类型用于表示真或假。
-
布尔值:
bool
let is_true: bool = true;
let is_false = false;
char
Rust 的字符类型用于表示单个Unicode字符,用单引号包裹。
- 字符:
char
let c: char = 'A';
let emoji = '😊';
不像其它语言,这意味着Rust的char并不是 1 个字节,而是 4 个。
整数类型
Rust 提供了多种整数类型,包括有符号和无符号整数。
- 有符号整数:
i8
,i16
,i32
,i64
,i128
,isize
- 范围:
i8
(-128 到 127),i16
(-32,768 到 32,767),i32
(-2,147,483,648 到 2,147,483,647),i64
和i128
具有更大的范围。 isize
:平台相关大小的整数(32位或64位)。
- 范围:
- 无符号整数:
u8
,u16
,u32
,u64
,u128
,usize
- 范围:
u8
(0 到 255),u16
(0 到 65,535),u32
(0 到 4,294,967,295),u64
和u128
具有更大的范围。 usize
:平台相关大小的无符号整数。
- 范围:
浮点类型
Rust 提供了单精度和双精度浮点类型。
- 单精度浮点数:
f32
- 双精度浮点数:
f64
(默认)
复合类型
元组 Tuples
元组可以将多个不同类型的值组合在一起。元组的长度是固定的。
let tup: (i32, f64, u8) = (500, 6.4, 1);
let (x, y, z) = tup; // 解构元组
println!("x: {}, y: {}, z: {}", x, y, z);
可以通过索引访问元组中的值,下标是从0开始:
let a = tup.0;
let b = tup.1;
let c = tup.2;
数组
数组包含一组相同类型的值,长度是固定的。
let arr: [i32; 3] = [1, 2, 3];
let first = arr[0];
let second = arr[1];
你还可以用相同的值初始化数组:
let arr = [3; 5]; // 等同于 [3, 3, 3, 3, 3]
切片 Slices
-
切片是对数组或字符串的一部分的引用 所以有
&
-
切片没有所有权,只是借用数组或字符串的一部分。
-
使用范围语法
[start..end]
来创建切片。 -
起始索引(start):包含在切片中,从0开始。
-
结束索引(end):排他性的(exclusive),即不包括在切片中。
let arr = [1, 2, 3, 4, 5];
let slice = &arr[1..3]; // 包含索引 1 和 2 的元素,即 2 和 3
println!("slice: {:?}", slice); // 输出: slice: [2, 3]
省略索引:
- 省略起始索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[..3]; // 等同于 &arr[0..3]
println!("slice: {:?}", slice); // 输出: slice: [1, 2, 3]
- 省略结束索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[2..]; // 等同于 &arr[2..5]
println!("slice: {:?}", slice); // 输出: slice: [3, 4, 5]
- 省略起始索引和结束索引
let arr = [1, 2, 3, 4, 5];
let slice = &arr[..]; // 等同于 &arr[0..5]
println!("slice: {:?}", slice); // 输出: slice: [1, 2, 3, 4, 5]
- 字符串切片
let s = String::from("hello world");
let hello = &s[..5]; // 等同于 &s[0..5]
let world = &s[6..]; // 等同于 &s[6..11]
let whole = &s[..]; // 等同于 &s[0..11]
println!("hello: {}", hello); // 输出: hello: hello
println!("world: {}", world); // 输出: world: world
println!("whole: {}", whole); // 输出: whole: hello world
str
str
是字符串切片类型,通常以引用的形式使用,即 &str
。
- 字符串切片 (
&str
):不可变的字符串引用,通常用来引用字符串字面量或String
类型的数据。 - 字符串 (
String
):可变的、拥有所有权的字符串类型, 允许字符串的修改和动态增长。
字符串切片 (&str
)
-
定义字符串切片
let greeting: &str = "Hello, world!";
println!("{}", greeting); // 输出: Hello, world!
String
类型
-
定义和修改
String
let mut s = String::from("Hello");
s.push_str(", world!"); // 添加一个字符串切片到 `String` 末尾
println!("{}", s); // 输出: Hello, world!
转换
-
String
转&str
let s = String::from("Hello");
let slice: &str = &s; // `String` 转 `&str`
println!("{}", slice); // 输出: Hello -
&str
转String
let slice: &str = "Hello, world!";
let s = String::from(slice); // `&str` 转 `String`
println!("{}", s); // 输出: Hello, world!
使用
-
字符串切片
let s = String::from("Hello, world!");
let hello = &s[0..5]; // 切片,包含索引 0 到 4 的字符
let world = &s[7..12]; // 切片,包含索引 7 到 11 的字符
println!("hello: {}, world: {}", hello, world); // 输出: hello: Hello, world: world