Skip to content

复合类型

字符串与切片

rust
let my_name = "Pascal";
// 在rust中  这样写是切片...

let s = String::from("hello world");

let hello = &s[..5];
let world = &s[6..11];
// 切片看起来长这样



let len = s.len();
let slice = &s[4..len];

// 也可以这样

let slice = &s[0..len];
let slice = &s[..];

// 如果要用的话,加上引用
fn main() {
    let s = String::from("hello,world!");
    say_hello(&s);
    say_hello(&s[..]);
    say_hello(s.as_str());
}

fn say_hello(s: &str) {
    println!("{}",s);
}

操作字符串

追加

rust
    let mut s = String::from("Hello ");

    s.push_str("rust");
    println!("追加字符串 push_str() -> {}", s);

    s.push('!');
    println!("追加字符 push() -> {}", s);

插入

rust
    let mut s = String::from("Hello ");

   s.insert(5, ',');
    println!("插入字符 insert() -> {}", s);

    s.insert_str(6, " I like");
    println!("插入字符串 insert_str() -> {}", s);

替换

rust

  // 替换 (Replace)
    // 该方法可适用于 String 和 &str 类型。replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。该方法是返回一个新的字符串,而不是操作原来的字符串。
    let string_replace = String::from("I like rust. Learning rust is my favorite!");
    let new_string_replace = string_replace.replace("rust", "RUST");





    //replacen
    // 该方法可适用于 String 和 &str 类型。replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。该方法是返回一个新的字符串,而不是操作原来的字符串。
     let string_replace = "I like rust. Learning rust is my favorite!";
    let new_string_replacen = string_replace.replacen("rust", "RUST", 1);




    // replace_range

    该方法仅适用于 String 类型。replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰。


    let mut string_replace_range = String::from("I like rust!");
    string_replace_range.replace_range(7..8, "R");

删除

rust
fn main() {

    // pop —— 删除并返回字符串的最后一个字符
    // 该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option 类型,如果字符串为空,则返回 None。

     let mut string_pop = String::from("rust pop 中文!");
    let p1 = string_pop.pop();
    let p2 = string_pop.pop();



    // remove —— 删除并返回字符串中指定位置的字符
    // 该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。


    let mut string_remove = String::from("测试remove方法");
    println!(
        "string_remove 占 {} 个字节",
        std::mem::size_of_val(string_remove.as_str())
    );
    // 删除第一个汉字
    string_remove.remove(0);
    // 下面代码会发生错误
    // string_remove.remove(1);
    // 直接删除第二个汉字
    // string_remove.remove(3);


    // truncate —— 删除字符串中从指定位置开始到结尾的全部字符
    // 该方法是直接操作原来的字符串。无返回值。
    let mut string_truncate = String::from("测试truncate");
    string_truncate.truncate(3);


    // clear —— 清空字符串
    // 该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。
     let mut string_clear = String::from("string clear");
    string_clear.clear();








}

连接

rust


    // 连接 (Concatenate)
     let string_append = String::from("hello ");
    let string_rust = String::from("rust");
    // &string_rust会自动解引用为&str
    let result = string_append + &string_rust;
    let mut result = result + "!"; // `result + "!"` 中的 `result` 是不可变的
    result += "!!!";

    println!("连接字符串 + -> {}", result);


// 使用 + 或者 += 连接字符串,要求右边的参数必须为字符串的切片引用(Slice)类型。其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰。

    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);

操作 UTF-8 字符串

rust
for c in "中国人".chars() {
    println!("{}", c);
}

// 中
// 国
// 人


for b in "中国人".bytes() {
    println!("{}", b);
}

// 228
// 184
// 173
// 229
// 155
// 189
// 228
// 186
// 186

对于 String 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的:

  • 首先向操作系统请求内存来存放 String 对象
  • 在使用完成后,将内存释放,归还给操作系统 其中第一部分由 String::from 完成,它创建了一个全新的 String。

重点来了,到了第二部分,就是百家齐放的环节,在有垃圾回收 GC 的语言中,GC 来负责标记并清除这些不再使用的内存对象,这个过程都是自动完成,无需开发者关心,非常简单好用;但是在无 GC 的语言中,需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的后果简直不可估量。

对于 Rust 而言,安全和性能是写到骨子里的核心特性,如果使用 GC,那么会牺牲性能;如果使用手动管理内存,那么会牺牲安全,这该怎么办?为此,Rust 的开发者想出了一个无比惊艳的办法:变量在离开作用域后,就自动释放其占用的内存:

rust
{
    let s = String::from("hello"); // 从此处起,s 是有效的

    // 使用 s
}                                  // 此作用域已结束,
                                   // s 不再有效,内存被释放

与其它系统编程语言的 free 函数相同,Rust 也提供了一个释放内存的函数: drop,但是不同的是,其它语言要手动调用 free 来释放每一个变量占用的内存,而 Rust 则在变量离开作用域时,自动调用 drop 函数: 上面代码中,Rust 在结尾的 } 处自动调用 drop。

元祖

元组是由多种类型组合到一起形成的,因此它是复合类型,元组的长度是固定的,元组中元素的顺序也是固定的。

在 Rust 中,元组(Tuple)常用于以下场景:

  • 返回多个值:当函数需要返回多个不同类型的值时,可以使用元组作为返回类型。元组允许将多个值打包在一起,并以单个返回值的形式返回。例如,可以使用元组返回一个函数的计算结果和状态信息。

  • 函数参数:当函数需要接受多个不同类型的参数时,可以使用元组作为函数的参数类型。通过将多个参数打包成一个元组,可以方便地传递和处理多个值。

  • 模式匹配:元组可以与模式匹配一起使用,用于解构和提取元组中的值。模式匹配允许根据元组的结构进行分支逻辑和条件判断。

  • 临时数据组织:如果需要将一些临时的、相关的数据组织在一起,但不需要为其定义一个专门的结构体,可以使用元组。元组提供了一种轻量级的方式来组织和传递相关的数据。

可以使用模式匹配或者 . 操作符来获取元组中的值。

rust
// 用模式匹配解构元组
fn main() {
    let tup = (500, 6.4, 1);

    let (x, y, z) = tup;

    println!("The value of y is: {}", y);
}


// .
fn main() {
    let x: (i32, f64, u8) = (500, 6.4, 1);

    let five_hundred = x.0;

    let six_point_four = x.1;

    let one = x.2;
}



// 以下是一些在 Rust 中使用元组的常见示例:
// 返回多个值:
fn calculate_stats(numbers: &[i32]) -> (i32, i32, i32) {
    let sum = numbers.iter().sum();
    let min = *numbers.iter().min().unwrap();
    let max = *numbers.iter().max().unwrap();
    (sum, min, max)
}

fn main() {
    let numbers = [1, 2, 3, 4, 5];
    let (sum, min, max) = calculate_stats(&numbers);
    println!("Sum: {}, Min: {}, Max: {}", sum, min, max);
}
// 函数参数:
fn process_person(name: &str, age: u32, address: &str) {
    // 处理逻辑
    println!("Name: {}, Age: {}, Address: {}", name, age, address);
}

fn main() {
    let person = ("Alice", 25, "123 Main St");
    process_person(person.0, person.1, person.2);
}
// 模式匹配:
fn process_result(result: Result<i32, &str>) {
    match result {
        Ok(value) => println!("Success: {}", value),
        Err(err) => println!("Error: {}", err),
    }
}

fn main() {
    let result1 = Ok(42);
    process_result(result1);

    let result2 = Err("Something went wrong");
    process_result(result2);
}
// 临时数据组织:
fn print_person_info(person: (&str, u32, &str)) {
    let (name, age, address) = person;
    println!("Name: {}, Age: {}, Address: {}", name, age, address);
}

fn main() {
    let person = ("Bob", 30, "456 Elm St");
    print_person_info(person);
}

结构体

Rust 中的 struct 可以类比为 JavaScript 中的对象,用于定义自定义的数据结构,具有字段、实例化、方法和可变性等特点。

一个结构体由几部分组成:

  • 通过关键字 struct 定义
  • 一个清晰明确的结构体 名称
  • 几个有名字的结构体 字段

有几点值得注意:

  • 初始化实例时,每个字段都需要进行初始化
  • 初始化时的字段顺序不需要和结构体定义时的顺序一致

通过 . 操作符即可访问结构体实例内部的字段值,也可以修改它们;

必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

简化结构体创建

rust
fn build_user(email: String, username: String) -> User {
    User {
        email,
        username,
        active: true,
        sign_in_count: 1,
    }
}

结构体更新

rust
let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };


let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };

..user1 必须在结构体的尾部使用。

把结构体中具有所有权的字段转移出去后,将无法再访问该字段,但是可以正常访问其它的字段。

元组结构体(Tuple Struct)

rust
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);

单元结构体(Unit-like Struct)

如果你定义一个类型,但是不关心该类型的内容, 只关心它的行为时,就可以使用 单元结构体:

rust
struct AlwaysEqual;

let subject = AlwaysEqual;

// 我们不关心 AlwaysEqual 的字段数据,只关心它的行为,因此将它声明为单元结构体,然后再为它实现某个特征
impl SomeTrait for AlwaysEqual {
//  impl SomeTrait 特征  后面会讲
}

结构体数据的所有权

结构体中使用引用,必须加上生命周期

使用 #[derive(Debug)] 来打印结构体的信息

打印结构体

rust
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

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

    println!("rect1 is {:?}", rect1);
}

// --------
// 或者用 dbg!
#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let scale = 2;
    let rect1 = Rectangle {
        width: dbg!(30 * scale),
        height: 50,
    };

    dbg!(&rect1);
}

枚举

rust
enum Status {
    IDEL,
    BUSY,
    Call,
}

fn main() {
    let mut status: Status = Status::IDEL;
    status = Status::BUSY;
    status = Status::Call;
    match status {
        Status::IDEL => {
            println!("Go IDEL");
        }
        Status::BUSY => {
            println!("Go BUSY");
        }
        Status::Call => {
            println!("Go Call");
        }
        // Direction::South => println!("Go South"),
        // Direction::East => println!("Go East"),
        // Direction::West => println!("Go West"),
    }
}

rust

fn divide(x: f64, y: f64) -> Option<f64> {
   if y != 0.0 {
       Some(x / y)
   } else {
       None
   }
 }
 
 fn main() {
   let result = divide(10.0, 0.0);
 
   match result {
       Some(value) => println!("Result: {}", value),
       None => println!("Cannot divide by zero"),
   }
 }

数组

rust 中,数组是 Rust 的基本类型,是固定长度的; 可变数组为动态数组 Vector 类型; array 是存储在栈上,Vector 是存储在堆上;

rust
let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];


let a: [i32; 5] = [1, 2, 3, 4, 5];

// let a = [3; 5];

let first = a[0]; // 获取a数组第一个元素

数组切片

数组切片允许我们引用数组的一部分:

rust

let a: [i32; 5] = [1, 2, 3, 4, 5];

let slice: &[i32] = &a[1..3];

assert_eq!(slice, &[2, 3]);
  • 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
  • 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
  • 切片类型[T]拥有不固定的大小,而切片引用类型&[T]则具有固定的大小,因为 Rust 很多时候都需要固定大小数据类型,因此&[T]更有用,&str 字符串切片也同理

总结

rust

fn main() {
  // 编译器自动推导出one的类型
  let one             = [1, 2, 3];
  // 显式类型标注
  let two: [u8; 3]    = [1, 2, 3];
  let blank1          = [0; 3];
  let blank2: [u8; 3] = [0; 3];

  // arrays是一个二维数组,其中每一个元素都是一个数组,元素类型是[u8; 3]
  let arrays: [[u8; 3]; 4]  = [one, two, blank1, blank2];

  // 借用arrays的元素用作循环中
  for a in &arrays {
    print!("{:?}: ", a);
    // 将a变成一个迭代器,用于循环
    // 你也可以直接用for n in a {}来进行循环
    for n in a.iter() {
      print!("\t{} + 10 = {}", n, n+10);
    }

    let mut sum = 0;
    // 0..a.len,是一个 Rust 的语法糖,其实就等于一个数组,元素是从0,1,2一直增加到到a.len-1
    for i in 0..a.len() {
      sum += a[i];
    }
    println!("\t({:?} = {})", a, sum);
  }
}