Skip to main content

Rust Book Datastructure - Micro

Rust
KIGA
Author
KIGA
This is a personal blog, intended for sharing.
Table of Contents

结构体
#

struct Name{
	name: String,
}
  1. 初始化实例时,每个字段都需要进行初始化
  2. 初始化时的字段顺序不需要和结构体定义时的顺序一致 3.修改-> 必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。

结构体赋值,所有权转移

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

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

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

    println!("user2: {}", user2.active);
    println!("user2: {}", user2.sign_in_count);
    println!("user2: {}", user2.email);
    println!("user2: {}", user2.username);
	
	  // 所有权转移但是active还是可以访问到
    println!("user1: {}", user1.active);

    // 报错
    println!("user1: {}", user1.username);
}
...
27 |       println!("user1: {}", user1.username);
   |                             ^^^^^^^^^^^^^^ value borrowed here after move

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

.. 语法表明凡是我们没有显式声明的字段,全部从 user1 中自动获取。需要注意的是 ..user1 
须在结构体的尾部使用。

实现了 Copy 特征的类型无需所有权转移,可以直接在赋值时进行 数据拷贝,其中 bool  u64 类型就实现了 Copy 特征,
因此 active  sign_in_count 字段在赋值给 user2 时,仅仅发生了拷贝,而不是所有权转移。

username 所有权被转移给了 user2 ,导致了 user1 无法再被使用,但是并不代表 user1 内部的其它字段不能被继续使用

结构体数据所有权
#

使用了自身拥有所有权的 String 类型而不是基于引用的 &str 字符串切片类型。这是一个有意而为之的选择:因为我们想要这个结构体拥有它所有 的数据,而不是从其它地方借用数据。

让 User 结构体从其它对象借用数据,不过这么做,就需要引入生命周期(lifetimes)这个新概念(生命周期能确保结构体的作用范围要比它所借用的数据的作用范围要小。)

在结构体中使用一个引用,就必须加上生命周期,否则就会报错

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

--> src/chapter_1.rs:3:12
  |
3 |     email: &str,
  |            ^ expected named lifetime parameter
  |
help: consider introducing a named lifetime parameter
  |
1 ~ struct User<'a> {
2 |     username: &str,
3 ~     email: &'a str,
  |

error: aborting due to 2 previous errors

--->
struct User<'a> { // 使用生命周期
    username: &'a str, 
}
fn main() {
    let user1 = User {
        username: "someone@example.com",
    };
    println!("user1's username is {}", user1.username);
	  //  println!("user1's username is {:#?}", user1); 当结构体较大时,有更好的输出表现
}

dbg! 宏
#

dbg! 宏,它会拿走表达式的所有权,然后打印出相应的文件名、行号等 debug 信息,最终还会把表达式值的所有权返回!

#[derive(Debug)]
struct Rectangle {
   width: u32,
   height: u32,
}
fn main() {
   let scale = 2;
   let rect1 = Rectangle {
       width: dbg!(30 * scale),
       height: 50,
   };
   dbg!(&rect1);
}
[src/chapter_1.rs:9] 30 * scale = 60
[src/chapter_1.rs:12] &rect1 = Rectangle {
   width: 60,
   height: 50,
}

枚举
#

任何类型的数据都可以放入枚举成员中
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);
}

Option 枚举用于处理空值
#

Option 枚举包含两个成员,一个成员表示含有值: Some(T) , 另一个表示没有值: None

T 是泛型参数, Some(T) 表示该枚举成员的数据类型是 T ,换句话说, Some 可以包含任何类型的数据。

enum Option<T> {
    Some(T),
    None,
}

通过match处理不同option情况

fn plus_one(x: Option<i32>) -> Option<i32> {
    match x {
        None => None,
        Some(i) => Some(i + 1),
    }
}
fn main() {
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
    dbg!(&six);
    dbg!(&none);
}
[src/chapter_1.rs:11] &six = Some(
    6,
)
[src/chapter_1.rs:12] &none = None

数组切片
#

fn main() {
    let a: [i32; 5] = [1, 2, 3, 4, 5];
    let slice: &[i32] = &a[1..3];
    assert_eq!(slice, &[2, 3]);
}

数组切片 slice 的类型是 &[i32] ,与之对比,数组的类型是 [i32;5]

  • 切片的长度可以与数组不同,并不是固定的,而是取决于你使用时指定的起始和结束位置
  • 创建切片的代价非常小,因为切片只是针对底层数组的一个引用
  • 切片类型[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 {
        println!("{:?}: ", a);
        // 将a变成一个迭代器,用于循环
        // 你也可以直接用for n in a {}来进行循环
        for n in a.iter() {
            println!("\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] ,因为后者有固定的类型大小

if
#

如果返回类型不一致就会报错(有多个分支能匹配,也只有第一个匹配的分支会被执行!)

let condition = true;
let number = if condition { 5 } else { "6" };
println!("The value of number is: {}", number);
error[E0308]: `if` and `else` have incompatible types
  --> exercises/intro/intro.rs:20:44
   |
20 |     let number = if condition { 5 } else { "6" };
   |                                 -          ^^^ expected integer, found `&str`
   |                                 |
   |                                 expected because of this

error: aborting due to previous error

For more information about this error, try `rustc --explain E0308`.

for
#

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

for item in &container {} 

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

for item in &mut collection
usage = 所有权
for item in collection for item in IntoIterator::into_iter(collection) 转移所有权
for item in &collection for item in collection.iter() 不可变借用
for item in &mut collection for item in collection.iter_mut() 可变借用

match
#

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

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

将一个表达式跟模式进行匹配,然后返回匹配的结果 true or false

#[derive(PartialEq)]
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));
}

通过序列 ..= 匹配值的范围
#

let x = 5;
match x {
	1..=5 => println!("one through five"),
	_ => println!("something else"),
}

let x = 'c';
match x {
	'a'..='j' => println!("early ASCII letter"),
	'k'..='z' => println!("late ASCII letter"),
	_ => println!("something else"),

解构数组
#

fn main() {
    // 定长数组
    let arr: [u16; 2] = [114, 514];
    let [x, y] = arr;
    assert_eq!(x, 114);
    assert_eq!(y, 514);

    // 不定长数组
    let arr: &[u16] = &[114, 514];
    if let [x, ..] = arr {
        assert_eq!(x, &114);
    }
    if let &[.., y] = arr {
        assert_eq!(y, 514);
    }
    let arr: &[u16] = &[];
    assert!(matches!(arr, [..]));
    assert!(!matches!(arr, [x, ..]));
}

@绑定
#

运算符允许为一个字段绑定另外一个变量

通过在 3..=7 之前指定 id_variable @ ,我们捕获了任何匹配此范围的值并同时将该值绑定到变量 id_variable 上

# @ 还可以在绑定新变量的同时,对目标进行解构
fn main() {
    enum Message {
        Hello { id: i32 },
    }
    let msg = Message::Hello { id: 5 };
    match msg {
        Message::Hello {
            id: id_variable @ 3..=7,
        } => {
            println!("Found an id in range: {}", id_variable)
        }
        Message::Hello { id: 10..=12 } => {
            println!("Found an id in another range")
        }
        Message::Hello { id } => {
            println!("Found some other id: {}", id)
        }
    }
}
#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}
fn main() {
    // 绑定新变量 `p`,同时对 `Point` 进行解构
    let p @ Point { x: px, y: py } = Point { x: 10, y: 23 };
    println!("x: {}, y: {}", px, py);
    println!("{:?}", p);
    let point = Point { x: 10, y: 5 };
    if let p @ Point { x: 10, y } = point {
        println!("x is 10 and y is {} in {:?}", y, p);
    } else {
        println!("x was not 10 :(");
    }
}
x: 10, y: 23
Point { x: 10, y: 23 }
x is 10 and y is 5 in Point { x: 10, y: 5 }