Rust语法入门

2023-04-17 20:21:19 浏览数 (1)

前言

Rust 是一种系统级编程语言,它的设计目标是提供高性能、安全性和并发性。Rust 的主要优势包括:

  1. 高性能:Rust 的编译器使用 LLVM 作为后端,能够生成高效的机器代码。同时,Rust 的内存管理机制可以避免内存泄漏和不安全的指针操作,进一步提高了程序的性能。
  2. 内存安全:Rust 的内存安全机制通过所有权和借用规则,避免了内存泄漏、野指针和缓冲区溢出等常见的安全问题。这使得 Rust 适用于编写高性能和安全性要求较高的系统级程序。
  3. 并发性:Rust 的所有权和借用规则,以及线程安全的数据类型,可以帮助开发者编写安全和高效的并发程序。
  4. 生态系统:Rust 拥有一个活跃的社区和丰富的包管理器 Cargo,开发者可以方便地使用已有的 Rust crate 来构建项目,也可以发布自己的 crate 给其他开发者使用。
  5. 可移植性:Rust 的编译器支持多种操作系统和 CPU 架构,可以在各种环境下编译和运行。

总之,Rust 是一种高性能、安全和并发的系统级编程语言,它的设计理念和特性使得它成为开发高性能、安全和可靠系统的优秀选择。

Rust安装

最后,请前往 https://www.rust-lang.org/zh-CN/tools/install 来安装 rustup (Rust 安装程序)。

请注意,为了使更改生效,您必须重新启动终端,在某些情况下需要重新启动 Windows 本身。

或者,您可以在 PowerShell 中使用 winget 命令安装程序:

代码语言:javascript复制
winget install --id Rustlang.Rustup

查看版本

代码语言:javascript复制
rustc --version

镜像

crates.io index失败

解决方法

使用Rust crates.io 索引镜像

编辑 ~/.cargo/config 文件,添加以下内容:

Windows下路径类似于

代码语言:javascript复制
C:UsersAdministrator.cargoconfig

config文件默认没有新建即可。

代码语言:javascript复制
[source.crates-io]
replace-with = 'tuna'

[source.tuna]
registry = "https://mirrors.tuna.tsinghua.edu.cn/git/crates.io-index.git"

该镜像可加快 cargo 读取软件包索引的速度。

开发工具

VSCode

插件

  • rust-analyzer

包管理器

Rust 的包管理器叫做 Cargo,它是 Rust 官方提供的工具,用于构建、测试和管理 Rust 项目的依赖关系。

Cargo不用另外安装,Rust内就包含。

Cargo 有以下几个主要的功能:

  • 管理 Rust 项目的依赖关系,可以自动下载、编译和安装依赖项。
  • 提供命令行工具来构建、运行和测试 Rust 项目。
  • 管理项目的版本号和发布。

下面是一些常用的 Cargo 命令:

  • cargo new <project-name>:创建一个新的 Rust 项目。
  • cargo build:编译项目,并生成可执行文件或库。
  • cargo run:编译并运行项目。
  • cargo test:运行项目的测试用例。
  • cargo doc:生成项目的文档。
  • cargo clean:清理项目的编译输出。

如果已存在文件夹,可以进入文件夹内运行

代码语言:javascript复制
cargo init

运行

代码语言:javascript复制
cargo run

除了管理项目本身的依赖关系,Cargo 还可以管理系统级别的 Rust 依赖库,例如 Rust 标准库和其他常用的 Rust crate。

在使用 Cargo 管理 Rust 项目时,通常需要编辑项目根目录下的 Cargo.toml 文件,这个文件包含了项目的依赖关系和其他一些配置信息。

总之,Cargo 是 Rust 生态系统中非常重要的一个组成部分,它的出现使得 Rust 项目的管理和构建变得更加简单和高效。

语法

打印

main.rs

代码语言:javascript复制
fn main() {
    println!("Hello, world!");
    print!("你好世界n");
    println!("Hello {}", "World");
}

变量和数据类型

Rust中的变量名必须以字母或下划线开头,并且可以包含字母、数字和下划线。变量必须先声明后使用。

Rust有以下基本数据类型:整型、浮点型、布尔型、字符型、元组和数组。

其中元组是不同类型值的集合,数组是相同类型值的集合。

声明变量

声明变量时需要使用 let 关键字,可以使用类型推断自动推断变量的类型:

代码语言:javascript复制
fn main() {
    let x = 5; // 自动推断为 i32 类型
    let y: f32 = 3.14; // 显式指定为 f32 类型
    let mut z = 10;
    z = 20;
    println!("x:{}", x);
    println!("y:{}", y);
    println!("z:{}", z);
}

字符串

字符串拼接

代码语言:javascript复制
fn main() {
    let s1 = "hello";
    let s2 = "world";
    println!("{}", concat_strings(&s1, &s2));
    println!("{}", s1);
    println!("{}", s2);
}

fn concat_strings(s1: &str, s2: &str) -> String {
    format!("{}-{}", s1, s2)
}

元组

元组是 Rust 中的一种复合类型,可以将多个不同类型的值组合在一起。元组的语法使用圆括号 (),元素之间使用逗号 , 分隔。元组的类型取决于其中元素的类型和数量。

下面是一个示例程序,演示了元组的用法:

代码语言:javascript复制
fn main() {
    // 定义一个元组,包含一个字符串和一个整数
    let t1: (&str, i32) = ("hello", 42);
    println!("t1={:?}", t1);

    // 从元组中获取元素
    let (s1, n1) = t1;
    println!("s1={}, n1={}", s1, n1);

    // 可以通过下标访问元组中的元素
    let s2 = t1.0;
    let n2 = t1.1;
    println!("s2={}, n2={}", s2, n2);

    // 可以使用元组解构来交换两个变量的值
    let mut x = 1;
    let mut y = 2;
    println!("x={}, y={}", x, y);
    (x, y) = (y, x);
    println!("x={}, y={}", x, y);
}

这个程序定义了一个包含一个字符串和一个整数的元组 t1,并将其输出到控制台。然后程序使用元组解构语法,分别将元组中的字符串和整数赋值给变量 s1n1,并将它们输出到控制台。接着程序通过下标访问元组中的元素,并将它们输出到控制台。最后,程序使用元组解构语法交换了变量 xy 的值。

数组

在 Rust 中,数组的长度是固定的,一旦定义了数组,其长度就无法更改。

Rust 提供了一个类似于数组的数据结构,称为 Vec,它可以动态调整大小。Vec 内部使用堆来存储数据,因此它可以在运行时动态增加或减少其容量。与数组不同,Vec 的长度可以根据需要增加或减少,因此在需要动态大小的情况下,使用 Vec 更为常见。

如果你需要在 Rust 中使用动态大小的数据结构,可以考虑使用 Vec。如果你需要在编译时确定大小并且不需要动态调整大小,则可以使用数组。

下面是一个 Rust 数组的示例:

代码语言:javascript复制
fn main() {
    // 创建一个包含5个元素的整数数组
    let arr: [i32; 5] = [1, 2, 3, 4, 5];

    // 访问数组中的元素
    println!("第一个元素是: {}", arr[0]);
    println!("第三个元素是: {}", arr[2]);

    // 遍历数组中的元素
    for i in 0..arr.len() {
        println!("第{}个元素是: {}", i, arr[i]);
    }
}

输出:

代码语言:javascript复制
第一个元素是: 1
第三个元素是: 3
第0个元素是: 1
第1个元素是: 2
第2个元素是: 3
第3个元素是: 4
第4个元素是: 5

在上面的示例中,我们创建了一个包含5个整数元素的数组。然后我们访问了数组中的元素,并使用 for 循环遍历了数组中的每个元素。请注意,在 Rust 中,数组的索引从0开始,而不是从1开始。

Vec

下面是一个 Rust Vec 的示例:

代码语言:javascript复制
fn main() {
    // 创建一个空的整数 Vec
    let mut vec: Vec<i32> = Vec::new();

    // 在 Vec 中添加元素
    vec.push(1);
    vec.push(2);
    vec.push(3);
    vec.push(4);
    vec.push(5);

    // 访问 Vec 中的元素
    println!("第一个元素是: {}", vec[0]);
    println!("第三个元素是: {}", vec[2]);

    // 遍历 Vec 中的元素
    for i in 0..vec.len() {
        println!("第{}个元素是: {}", i, vec[i]);
    }
}

输出:

代码语言:javascript复制
第一个元素是: 1
第三个元素是: 3
第0个元素是: 1
第1个元素是: 2
第2个元素是: 3
第3个元素是: 4
第4个元素是: 5

在上面的示例中,我们创建了一个空的 Vec<i32>,然后在其中添加了5个整数元素。我们访问了 Vec 中的元素,并使用 for 循环遍历了 Vec 中的每个元素。

请注意,在这个示例中我们使用了 mut 来声明 Vec 是可变的,因为我们要向其中添加元素。此外,在访问 Vec 中的元素时,我们使用了 [] 运算符来索引 Vec

Map

是的,Rust语言中提供了一种名为HashMap的Map实现,它允许开发人员使用键值对存储和检索数据。HashMap是Rust的标准库中的一部分,因此您无需安装任何其他库即可使用它。

要使用HashMap,您需要在Rust文件的顶部添加以下行来导入它:

代码语言:javascript复制
use std::collections::HashMap;

然后,您可以使用HashMap来创建一个新的映射,添加键值对,查找值等。以下是一些示例代码:

代码语言:javascript复制
use std::collections::HashMap;

fn main() {
    // 创建一个新的HashMap
    let mut map = HashMap::new();

    // 添加一些键值对
    map.insert("key1", "value1");
    map.insert("key2", "value2");
    map.insert("key3", "value3");

    // 查找值
    match map.get("key1") {
        Some(value) => println!("The value of key1 is {}", value),
        None => println!("Key1 not found"),
    }

    // 删除一个键值对
    map.remove("key2");

    // 迭代键值对
    for (key, value) in &map {
        println!("{}: {}", key, value);
    }
}

请注意,在Rust中,HashMap是通过哈希表实现的,因此您可以期望在平均情况下O(1)时间复杂度下执行插入、查找和删除操作。

函数

定义函数时需要使用 fn 关键字:

代码语言:javascript复制
fn add(x: i32, y: i32) -> i32 {
    x   y
}

其中 xy 是参数,-> i32 表示返回值类型为 i32。

方法

在 Rust 中,方法(method)和函数(function)都是非常类似的。

方法是一个与特定类型关联的函数,使用 impl 块来定义。

方法的第一个参数总是 self,表示调用这个方法的类型的实例。在方法内部,我们可以使用 self 访问这个实例的成员变量和方法。

在 Rust 中,方法参数的传递方式与函数一样,既可以传值(by value),也可以传引用(by reference)。当我们需要在方法内部修改实例的状态时,通常会使用可变引用(mutable reference)传递参数,以避免所有权的转移。

下面是一个简单的例子:

代码语言:javascript复制
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    // 定义一个方法来计算矩形面积
    fn area(&self) -> u32 {
        self.width * self.height
    }
    
    // 定义一个方法来调整矩形大小
    fn resize(&mut self, width: u32, height: u32) {
        self.width = width;
        self.height = height;
    }
}

fn main() {
    let mut rect = Rectangle { width: 10, height: 20 };
    println!("Area: {}", rect.area()); // 输出 "Area: 200"
    
    rect.resize(20, 30);
    println!("New area: {}", rect.area()); // 输出 "New area: 600"
}

在这个例子中,area 方法获取了 self 的不可变引用(&self),而 resize 方法获取了 self 的可变引用(&mut self)。这样可以在方法内部修改 self 的值,而不会转移所有权。如果我们使用值传递参数,那么在方法内部就无法修改 self 的值了。

需要注意的是,当我们在 impl 块内部定义方法时,必须为第一个参数使用 self 或者 &self 或者 &mut self,以指明这是一个方法。否则,它就是一个普通的函数。

控制流语句

Rust 的控制流语句包括 ifloopwhilefor 等。

代码语言:javascript复制
let x = 5;
if x < 10 {
    println!("x is less than 10");
} else {
    println!("x is greater than or equal to 10");
}

let mut i = 0;
while i < 10 {
    println!("{}", i);
    i  = 1;
}

for i in 0..10 {
    println!("{}", i);
}

结构体和枚举

Rust 支持定义结构体和枚举类型:

代码语言:javascript复制
struct Point {
    x: f32,
    y: f32,
}

enum Color {
    Red,
    Green,
    Blue,
}

引用和所有权

Rust 的核心特性之一是所有权,它规定每个值都有一个所有者,只有所有者才能修改或销毁这个值。

为了让其他变量也能引用这个值,可以使用引用:

代码语言:javascript复制
let mut x = 5;
let y = &x;  // y 是对 x 的引用
*x = 6;  // 错误!x 已经被借用
println!("{}", y);  // 5

示例2

代码语言:javascript复制
fn main() {
    let mut a = "11";
    let b = a;
    println!("a:{}", a);
    println!("b:{}", b);
    a = "22";
    println!("a:{}", a);
    println!("b:{}", b);
}

结果

a:11 b:11 a:22 b:11

我们可以看出

默认的赋值是赋予的值而非引用,只有引用才考虑只有一个所有者。

类?

Rust 语言中没有类(class)的概念,而是使用结构体(struct)和枚举(enum)等数据类型来构建复杂的数据结构。

Rust 的结构体(struct)可以包含字段(field)和方法(method),类似于其他面向对象语言中的类。

代码语言:javascript复制
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

fn main() {
    let rect_obj = Rectangle {
        width: 30,
        height: 50,
    };

    println!("结果:{}", rect_obj.area());
}

Trait(接口?)

以下是一个简单的Rust Trait示例代码:

代码语言:javascript复制
trait Printable {
    fn print(&self);
}

struct Point {
    x: i32,
    y: i32,
}

impl Printable for Point {
    fn print(&self) {
        println!("({}, {})", self.x, self.y);
    }
}

fn main() {
    let p = Point { x: 10, y: 20 };
    p.print();
}

在上面的示例中,我们定义了一个名为Printable的Trait,其中包含了一个print方法。然后我们定义了一个名为Point的结构体,它实现了Printable Trait,实现了Trait中的print方法。

最后在main函数中,我们创建了一个Point结构体的实例p,并调用了它的print方法。

当我们运行程序时,它将打印出点的坐标(10, 20)。

通过Trait,我们可以定义一些共同的行为,并在不同类型之间共享这些行为。这在Rust中非常有用,因为它允许我们编写可重用的代码,并在不同的类型之间实现一致的行为。

生命周期

以下是一个简单的Rust生命周期示例代码:

代码语言:javascript复制
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

fn main() {
    let s1 = String::from("hello");
    let s2 = "world";
    let result = longest(s1.as_str(), s2);
    println!("The longest string is {}", result);
}

在上面的示例中,我们定义了一个函数longest,它接受两个字符串的引用x和y,并返回其中较长的那个字符串的引用。在函数签名中,我们使用了生命周期参数'a来表明x和y参数的生命周期与返回值的生命周期是相同的。

在main函数中,我们创建了两个字符串s1和s2,其中s1是一个String类型的变量,s2是一个字符串字面量。然后我们调用longest函数,并将s1和s2的引用作为参数传递给它。最后,我们将返回值打印到控制台上。

当我们运行程序时,它将输出“ The longest string is world”,因为“world”比“hello”更长。

通过使用生命周期,我们可以在Rust中管理内存分配和释放的方式,以确保安全和正确的内存使用。在上面的示例中,我们使用生命周期'a来表明longest函数返回的引用是x和y参数的引用之一,以便编译器可以检查引用的有效性和生命周期的正确性。

错误处理

好的,下面是在Rust中处理错误的几种方式的示例代码:

使用match表达式

代码语言:javascript复制
use std::fs::File;
use std::io::{Error, Read};

fn read_file_contents(file_path: &str) -> Result<String, Error> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    let file_path = "test.txt";
    match read_file_contents(file_path) {
        Ok(contents) => println!("{}", contents),
        Err(err) => match err.kind() {
            ErrorKind::NotFound => println!("File not found."),
            _ => println!("Error reading file: {}", err),
        },
    }
}

在上面的示例中,我们定义了一个函数read_file_contents,它尝试打开一个文件并将其内容读取到字符串中。在main函数中,我们使用match表达式来处理read_file_contents返回的Result。如果返回的结果是Ok,则将文件内容打印到控制台上。如果返回的结果是Err,则根据错误类型进行处理,如果错误类型是NotFound,则打印“File not found.”,否则打印错误信息。

使用if let表达式

代码语言:javascript复制
use std::fs::File;
use std::io::{Error, Read};

fn read_file_contents(file_path: &str) -> Result<String, Error> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    let file_path = "test.txt";
    if let Ok(contents) = read_file_contents(file_path) {
        println!("{}", contents);
    } else {
        eprintln!("Error reading file.");
    }
}

在上面的示例中,我们使用if let表达式来处理read_file_contents返回的Result。如果返回的结果是Ok,则将文件内容打印到控制台上。否则,将错误信息打印到标准错误流上。

使用?操作符:

代码语言:javascript复制
use std::fs::File;
use std::io::{Error, Read};

fn read_file_contents(file_path: &str) -> Result<String, Error> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() -> Result<(), Error> {
    let file_path = "test.txt";
    let contents = read_file_contents(file_path)?;
    println!("{}", contents);
    Ok(())
}

在上面的示例中,我们将main函数的返回类型改为Result<(), Error>,并在函数中使用?操作符来处理read_file_contents返回的Result。

如果返回的结果是Ok,则将文件内容打印到控制台上。否则,将错误传递给调用方处理。

使用unwrap方法:

代码语言:javascript复制
use std::fs::File;
use std::io::{Error, Read};

fn read_file_contents(file_path: &str) -> Result<String, Error> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    let file_path = "test.txt";
    let contents = read_file_contents(file_path).unwrap();
    println!("{}", contents);
}

在上面的示例中,我们定义了一个函数read_file_contents,它尝试打开一个文件并将其内容读取到字符串中。在main函数中,我们使用unwrap方法来处理read_file_contents返回的Result。如果返回的结果是Ok,则将文件内容打印到控制台上。如果返回的结果是Err,则会触发panic,程序会终止运行。请注意,使用unwrap方法会使程序在出现错误时崩溃,因此在实际开发中,需要根据具体情况选择合适的错误处理方式。

使用expect方法

代码语言:javascript复制
use std::fs::File;
use std::io::{Error, Read};

fn read_file_contents(file_path: &str) -> Result<String, Error> {
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

fn main() {
    let file_path = "test.txt";
    let contents = read_file_contents(file_path).expect("Failed to read file.");
    println!("{}", contents);
}

在上面的示例中,我们使用expect方法来处理read_file_contents返回的Result。如果返回的结果是Ok,则将文件内容打印到控制台上。如果返回的结果是Err,则会打印错误信息并触发panic,程序会终止运行。

多线程

在 Rust 中,可以使用多线程来并发地操作 Vec

下面是一个使用 Rust 实现多线程操作 Vec 的示例代码:

代码语言:javascript复制
use std::thread;
use std::sync::{Arc, Mutex};

fn main() {
    let mut v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9];
    let v = Arc::new(Mutex::new(v));

    let mut handles = vec![];
    for i in 0..3 {
        let v = Arc::clone(&v);
        let handle = thread::spawn(move || {
            let mut v = v.lock().unwrap();
            for j in (i * 3)..((i   1) * 3) {
                v[j] *= 2;
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    let v = v.lock().unwrap();
    println!("{:?}", v);
}

该示例代码创建了一个包含 9 个元素的 Vec,然后使用 ArcMutex 包装了该 Vec。接着,我们创建了 3 个线程,每个线程负责修改 Vec 的三分之一元素的值。在每个线程的执行体中,我们使用 Mutex 来获取 Vec 的写锁,并修改 Vec 中的元素。最后,我们等待所有线程完成,并输出修改后的 Vec

需要注意的是,在使用多线程操作 Vec 时,需要注意避免数据竞争等问题。在上述示例代码中,我们使用了 ArcMutex 来保护 Vec 的访问,并确保了多个线程不会同时访问同一个元素,从而避免了数据竞争问题。同时,由于使用了 Mutex,并发性能可能会受到一定的影响,因此需要根据具体情况进行权衡和选择。

原理与概念

内存释放机制

在 Rust 中,所有的值都有一个所有者(owner),当所有者超出作用域时,该值将被自动释放。这种方式被称为所有权(ownership)系统。

Rust 会在编译时检查所有权规则,确保在程序运行时不会出现内存错误,如空指针引用和野指针等。

当我们不再需要一个对象时,可以通过让它的所有者离开作用域来释放它。例如:

代码语言:javascript复制
fn main() {
    let s = String::from("hello");  // s 是 String 类型的对象,它拥有 hello 字符串的所有权
    println!("{}", s);              // 打印 hello
}                                 // s 离开作用域,hello 字符串的所有权被释放

在上面的代码中,我们定义了一个 String 类型的对象 s,它拥有 hello 字符串的所有权。当 s 离开作用域时,Rust 会自动释放 hello 字符串的内存。

如果我们需要动态分配内存,则可以使用 Box 类型来管理内存。Box 是一个智能指针类型,它使用堆内存来存储数据,并负责释放内存。

例如:

代码语言:javascript复制
fn main() {
    let x = Box::new(5);  // 使用 Box 类型分配内存并存储数据 5
    println!("{}", x);    // 打印 5
}                        // x 离开作用域,Box 类型负责释放内存

在上面的代码中,我们使用 Box 类型分配内存并存储数据 5。当 x 离开作用域时,Box 类型会自动释放内存。

总之,Rust 的所有权系统保证了内存的安全和有效释放。通过在对象超出作用域时自动释放对象,Rust 避免了手动管理内存的复杂性和错误。

引用和所有权

在 Rust 中,每个值都有一个对应的所有者(owner),也就是控制这个值在内存中生命周期的变量。当这个变量离开作用域时,这个值也会被自动销毁。这样的话,就能够确保内存的安全性和避免一些常见的程序错误,比如空指针异常和数据竞争。

然而,在某些情况下,我们需要将值借用(borrow)给其他变量进行操作,而不是将所有权转移给他们。这时就需要用到 Rust 的引用(reference)机制。引用允许我们在不转移所有权的情况下,访问一个值的内部数据。

Rust 的引用有两种:可变引用(mutable reference)和不可变引用(immutable reference)。可变引用允许对值进行修改,而不可变引用则只允许读取。

为了获取一个引用,我们可以使用取引用运算符 &。比如:

代码语言:javascript复制
let x = 42;
let y = &x; // 不可变引用
let z = &mut x; // 可变引用,必须是 mut x

其中,y 是一个指向 x 的不可变引用,而 z 是一个指向 x 的可变引用。

需要注意的是,同一时间只能有一个可变引用,或者任意数量的不可变引用,但不能同时存在可变和不可变引用。这是为了避免数据竞争,保证内存安全性。

引用在函数参数传递中也很常见。

比如:

代码语言:javascript复制
fn foo(x: &i32) {
    // ...
}

let x = 42;
foo(&x); // 传递不可变引用

在函数参数中传递引用时,函数不会获取所有权,而是只能使用借用的值。这样可以减少内存使用,并且避免所有权的转移。

以上是 Rust 引用的基本概念和用法。如果您还有其他问题或者需要更深入的解释,请随时提出。

str与String

在 Rust 中,strString 是两种不同的字符串类型。

str 是一种不可变的字符串类型,通常作为字符串的引用来使用。str 类型的字符串通常是在代码中直接写出来的,比如 "hello""world" 等。

String 是一种可变的字符串类型,通常用于在运行时创建和修改字符串。我们可以使用 String::new() 方法创建一个空的 String 对象,然后使用 push_str()push()replace() 等方法来修改这个字符串对象的内容。通常我们会在运行时需要拼接、截取、替换等字符串操作时使用 String 类型。

需要注意的是,String 类型和 str 类型之间可以进行类型转换。我们可以使用 as_str() 方法将 String 类型转换为 &str 类型,或者使用 to_string() 方法将 &str 类型转换为 String 类型。

下面是一个示例程序,演示了 strString 的用法和类型转换:

代码语言:javascript复制
fn main() {
    // 使用 str 类型的字符串
    let s1: &str = "hello";
    let s2: &str = "world";
    println!("s1={}, s2={}", s1, s2);

    // 使用 String 类型的字符串
    let mut s3: String = String::new();
    s3.push_str(s1);
    s3.push(' ');
    s3.push_str(s2);
    println!("s3={}", s3);

    // 将 String 转换为 &str
    let s4: &str = s3.as_str();
    println!("s4={}", s4);

    // 将 &str 转换为 String
    let s5: String = s2.to_string();
    println!("s5={}", s5);
}

这个程序将输出以下内容:

代码语言:javascript复制
s1=hello, s2=world
s3=hello world
s4=hello world
s5=world

Vec和vec!

在Rust中,Vec是一个动态可增长的数组类型,vec则是一个Rust标准库中的宏,用于快速创建和初始化一个Vec类型的实例。

Vec类型提供了许多有用的方法,包括在数组末尾添加元素、从数组末尾删除元素、访问数组中的元素、对数组中的元素进行排序等。

vec!宏则是用于快速创建和初始化一个Vec类型的实例。

例如,下面的代码创建一个包含三个元素的Vec类型的实例:

代码语言:javascript复制
let v = vec![1, 2, 3];

这个代码与以下代码等价:

代码语言:javascript复制
let mut v = Vec::new();
v.push(1);
v.push(2);
v.push(3);

使用vec!宏可以减少代码量并提高可读性。

常用的宏

Rust是一种使用宏(macros)非常广泛的语言。宏是一种代码生成器,可以帮助我们消除重复的代码,并且可以帮助我们生成一些代码的变体,以适应不同的情况和需求。以下是一些Rust中常用的宏:

  1. println!format! - 用于打印输出和格式化字符串
  2. assert! - 用于测试和调试,确保一些条件是满足的
  3. vec! - 用于创建Vec类型的实例
  4. vec_deque! - 用于创建VecDeque类型的实例
  5. hash_map! - 用于创建HashMap类型的实例
  6. assert_eq! - 用于测试和调试,确保两个值相等
  7. cfg! - 用于编译时条件检查,根据不同的条件生成不同的代码
  8. env! - 用于读取环境变量的值
  9. concat! - 用于将多个字符串拼接成一个字符串
  10. include! - 用于将一个文件的内容嵌入到另一个文件中

这些宏是Rust编程中非常常用的一些宏,还有许多其他的宏可以在需要时使用。

0 人点赞