Rust 总结:模式匹配 match和if let

原文:https://course.rs/basic/match-pattern/match-if-let.html

模式匹配

enum Direction {
    East,
    West,
    North,
    South,
}

fn main() {
    let dire = Direction::South;
    match dire {
        Direction::East => println!("East"),
        Direction::North | Direction::South => {
            println!("South or North");
        },
        _ => println!("West"),
    };
}
  • match 的匹配必须要穷举出所有可能,因此这里用 _ 来代表未列出的所有可能性
  • match 的每一个分支都必须是一个表达式,且所有分支的表达式最终返回值的类型必须相同
  • X | Y,类似逻辑运算符 或,代表该分支可以匹配 X 也可以匹配 Y,只要满足一个即可
match target {
    模式1 => 表达式1,
    模式2 => {
        语句1;
        语句2;
        表达式2
    },
    _ => 表达式3
}

模式绑定

enum Action {
    Say(String),
    MoveTo(i32, i32),
    ChangeColorRGB(u16, u16, u16),
}

fn main() {
    let actions = [
        Action::Say("Hello Rust".to_string()),
        Action::MoveTo(1,2),
        Action::ChangeColorRGB(255,255,0),
    ];
    for action in actions {
        match action {
            Action::Say(s) => {
                println!("{}", s);
            },
            Action::MoveTo(x, y) => {
                println!("point from (0, 0) move to ({}, {})", x, y);
            },
            Action::ChangeColorRGB(r, g, _) => {
                println!("change color into '(r:{}, g:{}, b:0)', 'b' has been ignored",
                    r, g,
                );
            }
        }
    }
}

if let 匹配

当你只要匹配一个条件,且忽略其他条件时就用 if let ,否则都用 match

if let PATTERN = SOME_VALUE {

}
let v = Some(3u8);
match v {
    Some(3) => println!("three"),
    _ => (),
}

// --->

if let Some(3) = v {
    println!("three");
}

while let条件循环

#![allow(unused)]
fn main() {
  // Vec是动态数组
  let mut stack = Vec::new();

  // 向数组尾部插入元素
  stack.push(1);
  stack.push(2);
  stack.push(3);

  // stack.pop从数组尾部弹出元素
  while let Some(top) = stack.pop() {
      println!("{}", top);
  }
}

matches!宏

Rust 标准库中提供了一个非常实用的宏:matches!,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false

enum MyEnum {
    Foo,
    Bar
}

fn main() {
    let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::Foo];

    // 错误
    //v.iter().filter(|x| x == MyEnum::Foo);
    v.iter().filter(|x| matches!(x, MyEnum::Foo));

    // 更多例子
    let foo = 'f';
    assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));

    let bar = Some(4);
    assert!(matches!(bar, Some(x) if x > 2));
}

变量覆盖

fn main() {
   let age = Some(30);
   println!("在匹配前,age是{:?}",age);
   if let Some(age) = age {
       println!("匹配出来的age是{}",age);
   }

   println!("在匹配后,age是{:?}",age);
}

注意,match 中的变量覆盖其实不是那么的容易看出,因此要小心!

Rust 总结:流程控制

原文:https://course.rs/basic/flow-control.html

if

  • if 语句块是表达式
  • 用 if 来赋值时,如果返回类型不一致就会报错
let mut v = 0;
for i in 1..10 {
    v = if i == 9 {
        continue // 没有返回值
    } else {
        i
    }
}
println!("{}", v);

for

注意,使用 for 时我们往往使用集合的引用形式,除非你不想在后面的代码中继续使用该集合(比如我们这里使用了 container 的引用)。如果不使用引用的话,所有权会被转移(move)到 for 语句块中,后面就无法再使用这个集合了):

for item in &container {
  // ...
}

对于实现了 copy 特征的数组(例如 [i32; 10] )而言, for item in arr 并不会把 arr 的所有权转移,而是直接对其进行了拷贝,因此循环之后仍然可以使用 arr 。

如果想在循环中,修改该元素,可以使用 mut 关键字:

for item in &mut collection {
  // ...
}
使用方法等价使用方式所有权
for item in collectionfor item in IntoIterator::into_iter(collection)转移所有
for item in &collectionfor item in collection.iter()不可变借用
for item in &mut collectionfor item in collection.iter_mut()可变借用

如果想在循环中获取元素的索引使用iter().enumerate()

fn main() {
    let a = [4, 3, 2, 1];
    // `.iter()` 方法把 `a` 数组变成一个迭代器
    for (i, v) in a.iter().enumerate() {
        println!("第{}个元素是{}", i + 1, v);
    }
}

两种循环方式优劣对比

以下代码,使用了两种循环方式:

// 第一种
let collection = [1, 2, 3, 4, 5];
for i in 0..collection.len() {
  let item = collection[i];
  // ...
}

// 第二种
for item in collection {

}
  • 性能:第一种使用方式中 collection[index] 的索引访问,会因为边界检查(Bounds Checking)导致运行时的性能损耗 —— Rust 会检查并确认 index 是否落在集合内,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的
  • 安全:第一种方式里对 collection 的索引访问是非连续的,存在一定可能性在两次访问之间,collection 发生了变化,导致脏数据产生。而第二种直接迭代的方式是连续访问,因此不存在这种风险(这里是因为所有权吗?是的话可能要强调一下)

while

while和for对比

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    while index < 5 {
        println!("the value is: {}", a[index]);

        index = index + 1;
    }
}
fn main() {
    let a = [10, 20, 30, 40, 50];

    for element in a.iter() {
        println!("the value is: {}", element);
    }
}

for 并不会使用索引去访问数组,因此更安全也更简洁,同时避免 运行时的边界检查,性能更高。

loop

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}
  • break 可以单独使用,也可以带一个返回值,有些类似 return
  • loop 是一个表达式,因此可以返回一个值

Rust 总结:复合类型-数组

原文:https://course.rs/basic/compound-type/array.html

数组

  • 长度固定
  • 元素必须有相同的类型
  • 依次线性排列

创建

fn main() {
    let a = [1, 2, 3, 4, 5];
    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];
}

数组的类型是 [T; Length],就如你所看到的,数组的长度是类型签名的一部分,因此数组的长度必须在编译期就已知

fn create_arr(n: i32) {
    let arr = [1; n];
}

👆函数将报错,因为编译器无法在编译期知道 n 的具体大小。

fn main() {
    // 很多时候,我们可以忽略数组的部分类型,也可以忽略全部类型,让编译器帮助我们推导
    let arr0 = [1, 2, 3];
    let arr: [_; 3] = ['a', 'b', 'c'];

    // 填空
    // 数组分配在栈上, `std::mem::size_of_val` 函数会返回整个数组占用的内存空间
    // 数组中的每个 char 元素占用 4 字节的内存空间,因为在 Rust 中, char 是 Unicode 字符
    assert!(std::mem::size_of_val(&arr) == 12);
}

当类型为非基础类型,👇代码错误

let array = [String::from("rust is good!"); 8];

println!("{:#?}", array);

基本类型在Rust中赋值是以Copy的形式,这时候你就懂了吧,let array=[3;5]底层就是不断的Copy出来的,但很可惜复杂类型都没有深拷贝,只能一个个创建。

let array: [String; 8] = core::array::from_fn(|i| String::from("rust is good!"));

println!("{:#?}", array);

获取

fn main() {
    let names = [String::from("Sunfei"), "Sunface".to_string()];

    // `get` returns an Option<T>, it's safe to use
    let name0 = names.get(0).unwrap();

    // but indexing is not safe
    let _name1 = &names[1];
}

数组切片

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字符串切片也同理

总结

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);
  }
}
  • 数组类型容易跟数组切片混淆,[T;n]描述了一个数组的类型,而[T]描述了切片的类型, 因为切片是运行期的数据结构,它的长度无法在编译期得知,因此不能用[T;n]的形式去描述
  • [u8; 3]和[u8; 4]是不同的类型,数组的长度也是类型的一部分
  • 在实际开发中,使用最多的是数组切片[T],我们往往通过引用的方式去使用&[T],因为后者有固定的类型大小

Rust 总结:复合类型-枚举类型

原文:https://course.rs/basic/compound-type/enum.html

枚举

⭐️ 枚举类型是一个类型,它会包含所有可能的枚举成员, 而枚举值是该类型中的具体某个成员的实例。

enum PokerCard {
    Clubs(u8),
    Spades(u8),
    Diamonds(char),
    Hearts(char),
}

fn main() {
   let c1 = PokerCard::Spades(5);
   let c2 = PokerCard::Diamonds('A');
}
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

fn main() {
    let m1 = Message::Quit;
    let m2 = Message::Move{x:1,y:1};
    let m3 = Message::ChangeColor(255,255,0);
}

由于每个结构体都有自己的类型,因此我们无法在需要同一类型的地方进行使用,例如某个函数它的功能是接受消息并进行发送,那么用枚举的方式,就可以接收不同的消息,但是用结构体,该函数无法接受 4 个不同的结构体作为参数。

而且从代码规范角度来看,枚举的实现更简洁,代码内聚性更强,不像结构体的实现,分散在各个地方。

Option 处理空值

enum Option<T> {
    Some(T),
    None,
}
#![allow(unused)]
fn main() {
fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}

let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
}

枚举实现链表

// 填空,让代码运行
use crate::List::*;

enum List {
    // Cons: 链表中包含有值的节点,节点是元组类型,第一个元素是节点的值,第二个元素是指向下一个节点的指针
    Cons(u32, Box<List>),
    // Nil: 链表中的最后一个节点,用于说明链表的结束
    Nil,
}

// 为枚举实现一些方法
impl List {
    // 创建空的链表
    fn new() -> List {
        // 因为没有节点,所以直接返回 Nil 节点
        // 枚举成员 Nil 的类型是 List
        Nil
    }

    // 在老的链表前面新增一个节点,并返回新的链表
    fn prepend(self, elem: u32) -> Self {
        Cons(elem, Box::new(self))
    }

    // 返回链表的长度
    fn len(&self) -> u32 {
        match *self {
            // 这里我们不能拿走 tail 的所有权,因此需要获取它的引用
            Cons(_, ref tail) => 1 + tail.len(),
            // 空链表的长度为 0
            Nil => 0
        }
    }

    // 返回链表的字符串表现形式,用于打印输出
    fn stringify(&self) -> String {
        match *self {
            Cons(head, ref tail) => {
                // 递归生成字符串
                format!("{}, {}", head, tail.stringify())
            },
            Nil => {
                format!("Nil")
            },
        }
    }
}

fn main() {
    // 创建一个新的链表(也是空的)
    let mut list = List::new();

    // 添加一些元素
    list = list.prepend(1);
    list = list.prepend(2);
    list = list.prepend(3);

    // 打印列表的当前状态
    println!("链表的长度是: {}", list.len());
    println!("{}", list.stringify());
}

Rust 总结:复合结构-结构体

原文:https://course.rs/basic/compound-type/struct.html

结构体

  • 初始化实例时,每个字段都需要进行初始化
  • 初始化时的字段顺序不需要和结构体定义时的顺序一致
#[derive(Debug)]
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

let mut user1 = User {
    email: String::from("someone@example.com"),
    username: String::from("someusername123"),
    active: true,
    sign_in_count: 1,
};

// 访问
println!("{}", user1.username);

// 更新
user1.email = String::from("anotheremail@example.com");

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

// user1 的部分字段所有权被转移到 user2 中:
//    username 字段发生了所有权转移,作为结果,user1 无法再被使用。
// println!("{:?}", user1); // 错误
println!("{:?}", user1.email); // 但user1.email仍然可以访问
println!("{:?}", user2); // 注意与js的扩展语法不同,email未被user1的值覆盖

🌟 你可以在实例化一个结构体时将它整体标记为可变的,但是 Rust 不允许我们将结构体的某个字段专门指定为可变的.

当解构一个变量时,可以同时使用 move 和引用模式绑定的方式。当这么做时,部分 move 就会发生:变量中一部分的所有权被转移给其它变量,而另一部分我们获取了它的引用。

在这种情况下,原变量将无法再被使用,但是它没有转移所有权的那一部分依然可以使用,也就是之前被引用的那部分。

元组结构体(Tuple Struct)

    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)

struct AlwaysEqual;

let subject = AlwaysEqual;

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

}

结构体数据所有权

fn main() {
    #[derive(Debug)]
    struct Person {
        name: String,
        age: Box<u8>,
    }

    let person = Person {
        name: String::from("Alice"),
        age: Box::new(20),
    };

    // 通过这种解构式模式匹配,person.name 的所有权被转移给新的变量 `name`
    // 但是,这里 `age` 变量确是对 person.age 的引用, 这里 ref 的使用相当于: let age = &person.age
    let Person { name, ref age } = person;

    println!("The person's age is {}", age);

    println!("The person's name is {}", name);

    // Error! 原因是 person 的一部分已经被转移了所有权,因此我们无法再使用它
    //println!("The person struct is {:?}", person);

    // 虽然 `person` 作为一个整体无法再被使用,但是 `person.age` 依然可以使用
    println!("The person's age from person struct is {}", person.age);
}

结构体数据生命周期

报错,因结构体的字段不知道数据的生命周期

struct User {
    username: &str,
    email: &str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };
}

修复

#[derive(Debug)]
struct User<'a> {
    username: &'a str,
    email: &'a str,
    sign_in_count: u64,
    active: bool,
}

fn main() {
    let user1 = User {
        email: "someone@example.com",
        username: "someusername123",
        active: true,
        sign_in_count: 1,
    };

    println!("{:?}", user1); // 打印到stdout
    dbg!(user1); // 打印到stderr
}

Rust总结:复合类型-元组

原文:https://course.rs/basic/compound-type/tuple.html

元组

元组(Tuple)的长度是固定的,元组中元素的顺序也是固定的

let tup: (i32, f64, u8) = (500, 6.4, 1);

解构

let tup = (500, 6.4, 1);
let (x, y, z) = tup;

访问

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;
}

超长无法打印

fn main() {
    let too_long_tuple = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13);
    println!("too long tuple: {:?}", too_long_tuple);
}

Rust 代码片段:排序vec

原文:https://rust-lang-nursery.github.io/rust-cookbook/algorithms/sorting.html

排序整数

可以通过 vec::sort 排序,也可以使用 vec::sort_unstable 它的速度更快,但相等的元素不保证顺序。

fn main() {
    let mut vec = vec![1, 5, 10, 2, 15];

    vec.sort();

    assert_eq!(vec, vec![1, 2, 5, 10, 15]);
}

排序浮点数

fn main() {
    let mut vec = vec![1.1, 1.15, 5.5, 1.123, 2.0];

    vec.sort_by(|a, b| a.partial_cmp(b).unwrap());

    assert_eq!(vec, vec![1.1, 1.123, 1.15, 2.0, 5.5]);
}

排序结构体

使结构体能够排序(vec::sort()),需要使用 Eq, PartialEq, Ord, PartialOrd 四个特性;自定义排序用vec::sort_by

#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
struct Person {
    name: String,
    age: u32
}

impl Person {
    pubfn new(name: String, age: u32) -> Self {
        Person {
            name,
            age
        }
    }
}

fn main() {
    let mut people = vec![
        Person::new("Zoe".to_string(), 25),
        Person::new("Al".to_string(), 60),
        Person::new("John".to_string(), 1),
    ];

    // Sort people by derived natural order (Name and age)
    people.sort();

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("John".to_string(), 1),
            Person::new("Zoe".to_string(), 25),
        ]);

    // Sort people by age
    people.sort_by(|a, b| b.age.cmp(&a.age));

    assert_eq!(
        people,
        vec![
            Person::new("Al".to_string(), 60),
            Person::new("Zoe".to_string(), 25),
            Person::new("John".to_string(), 1),
        ]);

}

Rust总结:复合类型-字符串

原文:https://course.rs/basic/compound-type/string-slice.html

  • #![allow(unused_variables)] 编译器忽略未使用变量的警告
  • unimplemented!() 指明函数没有实现

切片

对于字符串而言,切片就是对 String 类型中某一部分的引用,String类型的切片就是&str

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

let slice: &str = &s[0..2];
let slice: &str = &s[..2];

在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界,例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

let s = "中国人";
let a = &s[0..2];
println!("{}",a);

👇 错误,在获得了不可变借用后,使用可变借用,然后又打印先前的不可变借用产生错误,两者不能共存。

fn main() {
    let mut s = String::from("hello world");

    let word = first_word(&s); // 获得不可用借用

    s.clear(); // error! // 获得可变借用

    println!("the first word is: {}", word); // 打印不可变借用
}
fn first_word(s: &String) -> &str {
    &s[..1]
}

修复上面的代码👇

fn main() {
    let mut s = String::from("hello world");

    // 这里, &s 是 `&String` 类型,但是 `first_word` 函数需要的是 `&str` 类型。
    // 尽管两个类型不一样,但是代码仍然可以工作,原因是 `&String` 会被隐式地转换成 `&str` 类型,如果大家想要知道更多,可以看看 Deref 章节: https://course.rs/advance/smart-pointer/deref.html
    let word = first_word(&s);

    println!("the first word is: {}", word);
    // 关键是调整了word的使用位置
    s.clear();
}
fn first_word(s: &str) -> &str {
    &s[..1]
}

其他切片

#![allow(unused)]
fn main() {
    let a = [1, 2, 3, 4, 5];

    let copy = a; // 复制,地址与a不同let slice1 = &a[..];
    let slice2 = &a[..2];

    println!("{:p}, {:p}, {:p}, {:p}", &a, &copy, slice1, slice2)
    // 0x7ffee4da2cf0, 0x7ffee4da2d04, 0x7ffee4da2cf0, 0x7ffee4da2cf0
}

一个切片引用占用了2个大小的内存空间( 从现在开始,为了简洁性考虑,如无特殊原因,我们统一使用切片来特指切片引用 )。 该切片的第一个字是指向数据的指针,第二个字是切片的长度。字的大小取决于处理器架构,例如在 x86-64 上,字的大小是 64 位也就是 8 个字节,那么一个切片引用就是 16 个字节大小

  • 切片签名 &[T]
  • 数组签名 [T; length]
fn main() {
    let arr: [char; 3] = ['中', '国', '人'];

    let slice = &arr[..2];

    // 修改数字 `8` 让代码工作
    // 小提示: 切片和数组不一样,它是引用。如果是数组的话,那下面的 `assert!` 将会通过: '中'和'国'是char类型,char类型是Unicode编码,大小固定为4字节,两个字符为8字节。
    assert!(std::mem::size_of_val(&slice) == 16);
}

字符串

⭐️ 字符串字面量切片

虽然 String 的底层是 Vec<u8> 也就是字节数组的形式存储的,但是它是基于 UTF-8 编码的字符序列。String 分配在堆上、可增长且不是以 null 结尾。

而 &str 是切片引用类型( &[u8] ),指向一个合法的 UTF-8 字符序列,总之,&str 和 String 的关系类似于 &[T] 和 Vec<T> 。

let s = "Hello, world!";
// ->let s: &str = "Hello, world!";
// s是不可变引用

str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串,当 Rust 用户提到字符串时,往往指的就是 String 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码。

String 与 &str 的转换

&str to String

  • String::from("hello,world")
  • "hello,world".to_string()

String to &str:

  • 取引用(切片)let slice1 = &s;
  • let slice1: &str = s.as_str();
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 不允许索引字符串

👇 报错

#![allow(unused)]
fn main() {
   let s1 = String::from("hello");
   let h = s1[0];
}

对字符串切片是危险的

操作字符串

追加(String可用)
push(char)/push_str(&str)

插入(String可用)
insert(idx: usize, char)/insert_str(idx: usize, &str)

替换(String/&str可用)
replace(needle: &str, haystack: &str)/replacen(needle: &str, haystack: &str, n)

替换范围(String可用)
replace_range(range, &str)

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

删除(String可用)

  • pop – 删除并返回字符串的最后一个字符。其返回值是一个 Option 类型,如果字符串为空,则返回 None
  • remove —— 删除并返回字符串中指定位置的字符,remove() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。
fn main() {
    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);
    dbg!(string_remove);
}
  • truncate —— 删除字符串中从指定位置开始到结尾的全部字符,无返回值
  • clear —— 清空字符串,相当于truncate(0)

连接字符串

  • 用 + 或 +=s = s1 + &s2 (s, s1, s2都是 String,&s2自动解引用为 &str 类型)
fn main() {
    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 += "!!!";

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

⚠️ 注意,之所以可以使用 + 连接字符串是因为,调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 +, 必须传递切片引用类型。不能直接传递 String 类型。+ 和 += 都是返回一个新的字符串。所以变量声明可以不需要 mut 关键字修饰

⚠️ add() 定义

fn add(self, s: &str) -> String

因此

fn main() {
    let s1 = String::from("hello,");
    let s2 = String::from("world!");
    // 在下句中,s1的所有权被转移走了,因此后面不能再使用s1
    let s3 = s1 + &s2;
    assert_eq!(s3,"hello,world!");
    // 下面的语句如果去掉注释,就会报错
    // println!("{}",s1);
}
  • 使用 format! 连接字符串
fn main() {
    let s1 = "hello";
    let s2 = String::from("rust");
    let s = format!("{} {}!", s1, s2);
    println!("{}", s);
}

转义

使用 \

fn main() {
    // 通过 \ + 字符的十六进制表示,转义输出一个字符
    let byte_escape = "I'm writing \x52\x75\x73\x74!";
    println!("What are you doing\x3F (\\x3F means ?) {}", byte_escape);

    // \u 可以输出一个 unicode 字符
    let unicode_codepoint = "\u{211D}";
    let character_name = "\"DOUBLE-STRUCK CAPITAL R\"";

    println!(
        "Unicode character {} (U+211D) is called {}",
        unicode_codepoint, character_name
    );

    // 换行了也会保持之前的字符串格式
    let long_string = "String literals
                        can span multiple lines.
                        The linebreak and indentation here ->\
                        <- can be escaped too!";
    println!("{}", long_string);
}

禁止转义 r"...",包含双引号 r#"..."#

fn main() {
    println!("{}", "hello \\x52\\x75\\x73\\x74");
    let raw_str = r"Escapes don't work here: \x3F \u{211D}";
    println!("{}", raw_str);

    // 如果字符串包含双引号,可以在开头和结尾加 #
    let quotes = r#"And then I said: "There is no escape!""#;
    println!("{}", quotes);

    // 如果还是有歧义,可以继续增加,没有限制
    let longer_delimiter = r###"A string with "# in it. And even "##!"###;
    println!("{}", longer_delimiter);
}

操作UTF-8字符串

遍历 char

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

遍历 byte(字节)

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

取子字符串:utf8_slice

习题

如果要使用 str 类型,只能配合 Box。 & 可以用来将 Box 转换为 &str 类型

fn main() {
    let s: Box<str> = "hello, world".into();
    greetings(s)
}

fn greetings(s: Box<str>) {
    println!("{}", &s)
}

fn main() {
    let s: Box<&str> = "hello, world".into();
    greetings(*s)
}

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

字节字符串 (Byte String)

use std::str;

fn main() {
    // 注意,这并不是 `&str` 类型了!
    let bytestring: &[u8; 21] = b"this is a byte string";


    // 字节数组没有实现 `Display` 特征,因此只能使用 `Debug` 的方式去打印
    println!("A byte string: {:?}", bytestring);

    // 字节数组也可以使用转义
    let escaped = b"\x52\x75\x73\x74 as bytes";
    // ...但是不支持 unicode 转义
    // let escaped = b"\u{211D} is not allowed";
    println!("Some escaped bytes: {:?}", escaped);


    // raw string
    let raw_bytestring = br"\u{211D} is not escaped here";
    println!("{:?}", raw_bytestring);

    // 将字节数组转成 `str` 类型可能会失败
    if let Ok(my_str) = str::from_utf8(raw_bytestring) {
        println!("And the same as text: '{}'", my_str);
    }

    let _quotes = br#"You can also use "fancier" formatting, \
                    like with normal raw strings"#;

    // 字节数组可以不是 UTF-8 格式
    let shift_jis = b"\x82\xe6\x82\xa8\x82\xb1\x82\xbb"; // "ようこそ" in SHIFT-JIS

    // 但是它们未必能转换成 `str` 类型
    match str::from_utf8(shift_jis) {
        Ok(my_str) => println!("Conversion successful: '{}'", my_str),
        Err(e) => println!("Conversion failed: {:?}", e),
    };
}

练习:

// 填空
fn main() {
    let mut s = String::new();
    __;

    let v = vec![104, 101, 108, 108, 111];

    // 将字节数组转换成 String
    let s1 = __;


    assert_eq!(s, s1);

    println!("Success!")
}

答案

// FILL in the blanks
fn main() {
    let mut s = String::new();
    s.push_str("hello");

    // some bytes, in a vector
    let v = vec![104, 101, 108, 108, 111];

    // Turn a bytes vector into a String
    // We know these bytes are valid, so we'll use `unwrap()`.
    let s1 = String::from_utf8(v).unwrap();


    assert_eq!(s, s1);

    println!("Success!")
}

utf8_slice

use utf8_slice;
fn main() {
    let s = "The 🚀 goes to the 🌑!";

    let rocket = utf8_slice::slice(s, 4, 5);
    // 结果是 "🚀"
}

参考

https://doc.rust-lang.org/std/string/struct.String.html

Rust 代码片段:生成随机数据

原文:https://rust-lang-nursery.github.io/rust-cookbook/algorithms/randomness.html

随机数字

use rand::Rng;

fn main() {
    letmut rng = rand::thread_rng();

    let n1: u8 = rng.gen();
    let n2: u16 = rng.gen();
    println!("Random u8: {}", n1);
    println!("Random u16: {}", n2);
    println!("Random u32: {}", rng.gen::<u32>());
    println!("Random i32: {}", rng.gen::<i32>());
    println!("Random float: {}", rng.gen::<f64>());
}

一定范围内的随机数

use rand::Rng;

fn main() {
    letmut rng = rand::thread_rng();
    println!("Integer: {}", rng.gen_range(0..10));
    println!("Float: {}", rng.gen_range(0.0..10.0));
}

下面这种在重复生成某一范围内的随机数方面效果一样,但速度更快。

use rand::distributions::{Distribution, Uniform};

fn main() {
    letmut rng = rand::thread_rng();
    let die = Uniform::from(1..7);

    loop {
        let throw = die.sample(&mut rng);
        println!("Roll the die: {}", throw);
        if throw == 6 {
            break;
        }
    }
}

用给定的分布生成随机数

use rand_distr::{Distribution, Normal, NormalError};
use rand::thread_rng;

fn main() -> Result<(), NormalError> {
    letmut rng = thread_rng();
    let normal = Normal::new(2.0, 3.0)?;
    let v = normal.sample(&mut rng);
    println!("{} is from a N(2, 9) distribution", v);
    Ok(())
}

用指定类型生成随机值

use rand::Rng;
use rand::distributions::{Distribution, Standard};

#[derive(Debug)]struct Point {
    x: i32,
    y: i32,
}

impl Distribution<Point> for Standard {
    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Point {
        let (rand_x, rand_y) = rng.gen();
        Point {
            x: rand_x,
            y: rand_y,
        }
    }
}

fn main() {
    letmut rng = rand::thread_rng();
    let rand_tuple = rng.gen::<(i32, bool, f64)>();
    let rand_point: Point = rng.gen();
    println!("Random tuple: {:?}", rand_tuple);
    println!("Random Point: {:?}", rand_point);
}

用字母表生成随机密码

use rand::{thread_rng, Rng};
use rand::distributions::Alphanumeric;

fn main() {
    let rand_string: String = thread_rng()
        .sample_iter(&Alphanumeric)
        .take(30)
        .map(char::from)
        .collect();

    println!("{}", rand_string);
}

用自定义字符范围生成随机密码

fn main() {
    use rand::Rng;
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\
                            abcdefghijklmnopqrstuvwxyz\
                            0123456789)(*&^%$#@!~";
    const PASSWORD_LEN: usize = 30;
    letmut rng = rand::thread_rng();

    let password: String = (0..PASSWORD_LEN)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] aschar
        })
        .collect();

    println!("{:?}", password);
}

Rust 总结:所有权/借用

原文:https://course.rs/basic/ownership/index.html

所有权

  • Rust 中每一个值都被一个变量所拥有,该变量被称为值的所有者
  • 一个值同时只能被一个变量所拥有,或者说一个值只能拥有一个所有者
  • 当所有者(变量)离开作用域范围时,这个值将被丢弃(drop)

实现了Copy Trait的类型(基本类型)

  • 所有整数类型,比如 u32。
  • 布尔类型,bool,它的值是 true 和 false。
  • 所有浮点数类型,比如 f64。
  • 字符类型,char。
  • 元组,当且仅当其包含的类型也都是 Copy 的时候。比如,(i32, i32) 是 Copy 的,但 (i32, String) 就不是。
  • 不可变引用 &T ,例如转移所有权中的最后一个例子,但是注意: 可变引用 &mut T 是不可以 Copy的
fn main() {
    let x: &str = "hello, world";
    // 所有权没有转移,只是y复制了x的引用地址
    let y = x;
    println!("{},{}",x,y);
}

借用

获取变量的引用,称之为借用(borrowing)

解引用:let x = 5;let y = &x; *y 来解出引用所指向的值(也就是解引用)

不可变引用

fn main() {
    let s1 = String::from("hello");

    let len = calculate_length(&s1);

    println!("The length of '{}' is {}.", s1, len);
}

fn calculate_length(s: &String) -> usize {
    s.len()
}

ref

fn main() {
    let c = '中';

    let r1 = &c;
    // fill the blank,dont change other code
    let ref r2 = c;

    assert_eq!(*r1, *r2);

    // check the equality of the two address strings
    assert_eq!(get_addr(r1),get_addr(r2));
}

// get memory address string
fn get_addr(r: &char) -> String {
    format!("{:p}", r)
}

可变引用

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

fn change(some_string: &mut String) {
    some_string.push_str(", world");
}

同一作用域,特定数据只能有一个可变引用

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

let r1 = &mut s;
// r1 作用域已经结束
let r2 = &mut s;

println!("{}, {}", r1, r2); // 报错

可变引用与不可变引用不能同时存在

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

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题

println!("{}, {}, and {}", r1, r2, r3); // 因为后续用到了r1,r2

NLL:Non-Lexical Lifetimes(NLL) 专门用于找到某个引用在作用域(})结束前就不再被使用的代码位置。

悬垂引用(Dangling References)

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s // 错误
}

总结

  • 同一时刻,你只能拥有要么一个可变引用, 要么任意多个不可变引用
  • 引用必须总是有效的