MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Rust掌握高级类型与表达式

2023-04-084.1k 阅读

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 类型的字段 xy。在 main 函数中,我们创建了 p1 实例并访问其字段。然后,使用更新语法从 p1 创建了 p2p2x 字段被更新为 30y 字段继承自 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 枚举,它有三个可能的值 RedGreenBlueprint_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 携带两个 i32Write 携带一个 StringChangeColor 携带三个 i32handle_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 方法。CircleRectangle 结构体都实现了 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 方法实现。NewsArticleTweet 结构体都实现了 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 letwhile let 表达式 if letmatch 表达式的一种简洁形式,用于匹配单个模式。

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 语句定义的变量 xy 的作用域仅限于块内部。

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! 宏根据模板字符串和提供的参数创建一个新的 Stringprint!println! 宏类似,但它们将格式化后的字符串输出到控制台,println! 会在结尾添加换行符。

通过深入了解 Rust 的高级类型和表达式,你可以编写出更加灵活、高效且安全的 Rust 程序,充分发挥 Rust 语言在系统编程、Web 开发、数据科学等众多领域的优势。无论是复杂的自定义类型系统,还是丰富多样的表达式,都为 Rust 开发者提供了强大的工具来实现各种功能需求。在实际项目中,合理运用这些知识能够提升代码的可读性、可维护性以及性能,使 Rust 代码更加优雅和健壮。