Rust掌握高级类型与表达式
Rust 中的高级类型
1. 自定义类型:结构体(Structures)
结构体是 Rust 中一种非常强大的自定义类型,它允许你将多个相关的数据组合在一起。结构体定义了数据的结构,每个结构体实例都有自己的数据副本。
示例:定义和使用结构体
// 定义一个结构体
struct Point {
x: i32,
y: i32,
}
fn main() {
// 创建结构体实例
let p1 = Point { x: 10, y: 20 };
// 访问结构体字段
println!("The point is at ({}, {})", p1.x, p1.y);
// 使用结构体更新语法创建新实例
let p2 = Point { x: 30, ..p1 };
println!("The new point is at ({}, {})", p2.x, p2.y);
}
在上述代码中,我们定义了 Point
结构体,它有两个 i32
类型的字段 x
和 y
。在 main
函数中,我们创建了 p1
实例并访问其字段。然后,使用更新语法从 p1
创建了 p2
,p2
的 x
字段被更新为 30
,y
字段继承自 p1
。
结构体方法
结构体可以有自己的方法,这些方法定义在 impl
块中。
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
let rect2 = Rectangle { width: 10, height: 40 };
println!("Rectangle 1 area: {}", rect1.area());
println!("Rectangle 1 can hold Rectangle 2: {}", rect1.can_hold(&rect2));
}
这里 Rectangle
结构体有两个方法:area
计算矩形面积,can_hold
判断一个矩形是否能容纳另一个矩形。注意方法中 &self
表示借用结构体实例,这样可以避免复制整个结构体。
2. 枚举(Enums)
枚举允许你定义一个可以取一组固定值之一的类型。
简单枚举示例
enum Color {
Red,
Green,
Blue,
}
fn print_color(color: Color) {
match color {
Color::Red => println!("It's red"),
Color::Green => println!("It's green"),
Color::Blue => println!("It's blue"),
}
}
fn main() {
let my_color = Color::Green;
print_color(my_color);
}
在这个例子中,我们定义了 Color
枚举,它有三个可能的值 Red
、Green
和 Blue
。print_color
函数使用 match
表达式来根据不同的颜色值进行不同的操作。
带数据的枚举 枚举变体可以携带数据,这使得枚举更加灵活。
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn handle_message(message: Message) {
match message {
Message::Quit => println!("Received Quit message"),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => println!("Changing color to RGB({}, {}, {})", r, g, b),
}
}
fn main() {
let msg1 = Message::Quit;
let msg2 = Message::Move { x: 10, y: 20 };
let msg3 = Message::Write(String::from("Hello, Rust!"));
let msg4 = Message::ChangeColor(255, 0, 0);
handle_message(msg1);
handle_message(msg2);
handle_message(msg3);
handle_message(msg4);
}
这里 Message
枚举的每个变体都有不同的数据类型:Quit
没有数据,Move
携带两个 i32
,Write
携带一个 String
,ChangeColor
携带三个 i32
。handle_message
函数通过 match
来处理不同的消息变体。
3. 泛型类型(Generic Types)
泛型允许你编写可以处理多种类型的代码,而不是为每种类型都编写重复的代码。
泛型函数示例
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let numbers = vec![10, 20, 30];
let result = largest(&numbers);
println!("The largest number is {}", result);
let chars = vec!['a', 'b', 'c'];
let char_result = largest(&chars);
println!("The largest char is {}", char_result);
}
largest
函数是一个泛型函数,它接受一个 T
类型的切片,并返回切片中最大的元素。T: std::cmp::PartialOrd
是一个类型约束,它要求 T
类型实现 PartialOrd
特征,这样才能进行比较操作。
泛型结构体和枚举 结构体和枚举也可以是泛型的。
struct Pair<T, U> {
first: T,
second: U,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
fn main() {
let pair = Pair { first: 10, second: "Hello" };
let result: Result<i32, &str> = Result::Ok(42);
match result {
Result::Ok(value) => println!("The value is {}", value),
Result::Err(error) => println!("Error: {}", error),
}
}
Pair
结构体是一个泛型结构体,它可以存储不同类型的数据。Result
枚举也是泛型的,Ok
变体携带成功的值,Err
变体携带错误信息。
4. 特征(Traits)
特征定义了一组方法签名,类型可以通过实现特征来表明它支持这些方法。
定义和实现特征
trait Draw {
fn draw(&self);
}
struct Circle {
radius: i32,
}
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle with radius {}", self.radius);
}
}
struct Rectangle {
width: i32,
height: i32,
}
impl Draw for Rectangle {
fn draw(&self) {
println!("Drawing a rectangle with width {} and height {}", self.width, self.height);
}
}
fn draw_shapes(shapes: &[&impl Draw]) {
for shape in shapes {
shape.draw();
}
}
fn main() {
let circle = Circle { radius: 5 };
let rectangle = Rectangle { width: 10, height: 20 };
let shapes = vec![&circle, &rectangle];
draw_shapes(&shapes);
}
这里我们定义了 Draw
特征,它有一个 draw
方法。Circle
和 Rectangle
结构体都实现了 Draw
特征。draw_shapes
函数接受一个实现了 Draw
特征的切片,并调用每个形状的 draw
方法。
特征约束和默认实现 特征可以有默认实现,类型实现特征时可以选择覆盖默认实现。
trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
struct NewsArticle {
headline: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, {}", self.headline, self.content)
}
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Rust News"),
content: String::from("Learn about Rust's new features"),
};
let tweet = Tweet {
username: String::from("rust_user"),
content: String::from("Just learning Rust! #rust"),
};
println!("Article summary: {}", article.summarize());
println!("Tweet summary: {}", tweet.summarize());
}
Summary
特征有一个默认的 summarize
方法实现。NewsArticle
和 Tweet
结构体都实现了 Summary
特征,并覆盖了默认实现以提供自定义的摘要。
Rust 中的高级表达式
1. 控制流表达式
if - else
表达式
if - else
表达式在 Rust 中用于条件执行代码块。与其他语言不同,Rust 的 if
是一个表达式,它会返回一个值。
fn main() {
let num = 10;
let result = if num > 5 {
"Greater than 5"
} else {
"Less than or equal to 5"
};
println!("The result is: {}", result);
}
在这个例子中,if - else
表达式根据 num
的值返回不同的字符串,并且这个返回值被赋值给 result
变量。
match
表达式
match
表达式是 Rust 中强大的模式匹配工具,它可以匹配各种值的模式。
fn main() {
let num = 3;
match num {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Other number"),
}
}
这里 match
表达式根据 num
的值匹配不同的分支。_
是一个通配符,表示匹配其他所有情况。
if let
和 while let
表达式
if let
是 match
表达式的一种简洁形式,用于匹配单个模式。
fn main() {
let maybe_number = Some(5);
if let Some(num) = maybe_number {
println!("The number is: {}", num);
} else {
println!("No number found");
}
}
if let
尝试将 maybe_number
匹配为 Some(num)
模式,如果匹配成功则执行相应代码块。while let
类似,用于在循环中匹配模式。
fn main() {
let mut numbers = vec![1, 2, 3].into_iter();
while let Some(num) = numbers.next() {
println!("Number: {}", num);
}
}
这里 while let
循环从迭代器 numbers
中逐个获取值并打印,直到迭代器耗尽。
2. 函数调用表达式
函数调用是 Rust 中常见的表达式。函数可以接受参数并返回值。
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let result = add(3, 5);
println!("The result of addition is: {}", result);
}
add
函数接受两个 i32
类型的参数并返回它们的和。在 main
函数中,我们调用 add
函数并将返回值赋给 result
变量。
闭包(Closures) 闭包是一种匿名函数,可以捕获其环境中的变量。
fn main() {
let x = 10;
let add_x = |y: i32| x + y;
let result = add_x(5);
println!("The result is: {}", result);
}
这里 add_x
是一个闭包,它捕获了 x
变量。闭包的参数列表用 |
包围,|y: i32|
表示接受一个 i32
类型的参数 y
,闭包体 x + y
使用了捕获的 x
变量。
3. 操作符表达式
算术操作符
Rust 支持常见的算术操作符,如 +
、-
、*
、/
和 %
。
fn main() {
let a = 10;
let b = 3;
let sum = a + b;
let difference = a - b;
let product = a * b;
let quotient = a / b;
let remainder = a % b;
println!("Sum: {}", sum);
println!("Difference: {}", difference);
println!("Product: {}", product);
println!("Quotient: {}", quotient);
println!("Remainder: {}", remainder);
}
这些操作符按照预期对整数进行加、减、乘、除和取余运算。
逻辑操作符
逻辑操作符 &&
(逻辑与)、||
(逻辑或)和 !
(逻辑非)用于组合和反转布尔值。
fn main() {
let a = true;
let b = false;
let and_result = a && b;
let or_result = a || b;
let not_result =!a;
println!("And result: {}", and_result);
println!("Or result: {}", or_result);
println!("Not result: {}", not_result);
}
这里展示了逻辑操作符对布尔值的操作结果。
位操作符
位操作符用于对整数的二进制位进行操作,如 &
(按位与)、|
(按位或)、^
(按位异或)、!
(按位取反)、<<
(左移)和 >>
(右移)。
fn main() {
let a: u8 = 5; // 二进制: 00000101
let b: u8 = 3; // 二进制: 00000011
let and_result = a & b; // 二进制: 00000001
let or_result = a | b; // 二进制: 00000111
let xor_result = a ^ b; // 二进制: 00000110
let not_result =!a; // 二进制: 11111010
let left_shift = a << 2; // 二进制: 00010100
let right_shift = a >> 1; // 二进制: 00000010
println!("And result: {}", and_result);
println!("Or result: {}", or_result);
println!("Xor result: {}", xor_result);
println!("Not result: {}", not_result);
println!("Left shift result: {}", left_shift);
println!("Right shift result: {}", right_shift);
}
这些位操作符对 u8
类型的整数进行按位操作,并输出结果。
4. 块表达式
块表达式是由花括号 {}
包围的一系列语句。块表达式的值是块中最后一个表达式的值。
fn main() {
let result = {
let x = 3;
let y = 5;
x + y
};
println!("The result is: {}", result);
}
在这个例子中,块表达式计算 x + y
的值,并将这个值赋给 result
变量。块中的 let
语句定义的变量 x
和 y
的作用域仅限于块内部。
5. 数组和切片表达式
数组表达式 数组是固定大小的相同类型元素的集合。
fn main() {
let numbers: [i32; 5] = [1, 2, 3, 4, 5];
let first = numbers[0];
println!("The first number is: {}", first);
}
这里我们定义了一个包含 5 个 i32
类型元素的数组 numbers
,并通过索引访问第一个元素。
切片表达式 切片是对数组或其他连续内存区域的引用,它允许你引用数组的一部分。
fn main() {
let numbers = [1, 2, 3, 4, 5];
let slice = &numbers[1..3];
for num in slice {
println!("Number in slice: {}", num);
}
}
&numbers[1..3]
创建了一个从索引 1 到索引 3(不包括 3)的切片,然后我们遍历这个切片并打印其中的元素。
6. 字符串表达式
字符串字面量 字符串字面量是不可变的 UTF - 8 编码的文本。
fn main() {
let hello = "Hello, Rust!";
println!("{}", hello);
}
这里 hello
是一个字符串字面量,它的类型是 &str
(字符串切片)。
String
类型
String
类型是可变的字符串,它在堆上分配内存。
fn main() {
let mut s = String::from("Hello");
s.push_str(", Rust!");
println!("{}", s);
}
我们通过 String::from
创建一个 String
实例 s
,然后使用 push_str
方法在字符串后面追加内容。
字符串格式化
Rust 提供了多种字符串格式化方式,如 format!
、print!
和 println!
宏。
fn main() {
let name = "Rust";
let version = 1.0;
let message = format!("Welcome to {} version {}", name, version);
println!("{}", message);
}
format!
宏根据模板字符串和提供的参数创建一个新的 String
。print!
和 println!
宏类似,但它们将格式化后的字符串输出到控制台,println!
会在结尾添加换行符。
通过深入了解 Rust 的高级类型和表达式,你可以编写出更加灵活、高效且安全的 Rust 程序,充分发挥 Rust 语言在系统编程、Web 开发、数据科学等众多领域的优势。无论是复杂的自定义类型系统,还是丰富多样的表达式,都为 Rust 开发者提供了强大的工具来实现各种功能需求。在实际项目中,合理运用这些知识能够提升代码的可读性、可维护性以及性能,使 Rust 代码更加优雅和健壮。