创建自定义类型的两种方式

结构体

1、基本概念

对比元组

  • 二者每一个部分可以不同类型
  • 结构体需要对各个部分命名,元组不用

通过定义的结构体创建实例
结构体实例可变的话,可以修改结构体中的值

  • 整个实例都要可变,Rust不允许只可变某个字段

    2、结构体数据的所有权——有引用字段的结构体

    结构体内部可以存引用,这个引用可能是其他对象拥有的数据

  • 如果这么做,就需要有 生!命!周!期!

  • 生命周期确保结构体引用的数据有效性和结构体本身保持一致
  • 如果结构体中存一个引用,而不指定生命周期,那就是无效的

    3、基本代码示例

    简单定义

    1. struct User {
    2. username: String,
    3. age: u32,
    4. active: bool,
    5. }

    创建实例:可变和不可变

    1. //不可变
    2. let user1 = User {
    3. username: String::from("xxx"),
    4. age: 2,
    5. active: false,
    6. };
    7. //可变
    8. let mut user1 = User {
    9. username: String::from("xxx"),
    10. age: 2,
    11. active: false,
    12. };
    13. user1.age = 2;

    函数返回实例

  • 字段初始化简写语法

    1. fn build_user(username: String) -> User {
    2. User {
    3. username, //参数名和字段名相同时可以简化写法
    4. active: true,
    5. age: 1,
    6. }
    7. }

    从其他实例创建实例

  • 结构体更新语法

    1. let user2 = User {
    2. username: String::from("lpc"),
    3. ..user1 //其余实例字段同user1
    4. };

    使用无命名字段元组结构体

  • 没有字段名,只有字段类型

  • 用于给整个元组取名,让这个元组和其他元组不同

    • 下面的black和p虽然数据一样,但是不同类型
      1. struct Color(i32, i32, i32);
      2. struct Point(i32, i32, i32);
      3. let black = Color(0, 0, 0);
      4. let p = Point(0, 0, 0);

      没有字段的类单元结构体unit-like structs

  • 我们可能想在某个类型上实现trait,但不想在类型中存数据

    4、在结构体中使用方法

    方法 和 函数

  • 二者类似,使用fn关键字,拥有参数和返回值

  • 方法是在结构体上下文中定义的(或者枚举和trait对象)
  • 方法第一个参数总是self

    示例

  • &self是用来代替 rect: &Rect 的,表示不获取所有权,只想读取数据

  • &mut self可以在方法中修改调用方法的实例

    1. #[derive(Debug)]
    2. struct Rect {
    3. width: u32,
    4. height: u32,
    5. }
    6. impl Rect {
    7. fn area(&self) -> u32 {
    8. self.width * self.height
    9. }
    10. }

    自动引用和解引用

  • obj.func() 时,Rust会自动给obj添加 &\&mut* 这些符号

  • 因为我们已经有了obj实例self和方法名,Rust可以自动推断出是 只读 &self / 修改&mut self / 获取所有权self 中的哪一种

    关联函数

  • 之前我们说方法是要有impl+&self,于是我如果不想传入&self,光去impl行不行

  • 关联函数和结构体关联,但仍然是函数,不做用于结构体实例,如String::from
  • 关联函数常用于构造函数,返回新的实例

    1. impl Rect {
    2. fn square(size: u32) -> Rect {
    3. Rect {width:size, height:size}
    4. }
    5. }
    6. let sq = Rect::square(3);

    枚举

    通过列举可能成员variants来定义一个类型
    很多语言都有枚举,但区别挺大的
    Rust枚举和haskell中的代数数据类型algebraic data types很像

    1、定义枚举

    考虑场景,一个IP要么是v4要么是v6,经常需要区分开处理;但他们都是IP,也经常需要当成同一种类型
    C语言常用做法:强行拆分开

    1. enum kind {v4, v6};
    2. struct Ip {kind k; char* address};
    3. Ip p4, p6;
    4. p4.kind = kind.v4;
    5. p6.kind = kind.v6;
    6. ...

    Rust中可以很灵活

    1. enum IpAddr {
    2. V4(u8, u8, u8, u8),
    3. V6(String),
    4. }
    5. let home = IpAddr::V4(127, 0, 0, 1);
    6. let loopback = IpAddr::V6(String::from("::1"));

    一个看上去奇怪但确实有用的枚举Message

  • 创建一个枚举实例的时候看起来就有点像关联函数

    1. enum Message {
    2. Quit,
    3. Move {x:i32, y:i32},
    4. Write(String),
    5. ChangeColor(i32, i32, i32),
    6. }
    7. impl Message {
    8. fn call(&self){}
    9. }
    10. let m = Message::Write(String::from("hello"));
    11. m.call();

    2、Option枚举,很强,类型安全

    是标准库定义的另一个枚举
    对应场景:一个值,他要么没值,要么有值
    类型系统:编译器需要检查是否处理了所有应该处理的情况,这样可以避免常见bug
    Rust中没有空值NULL,而是使用Option
    Option太常用,放在了prelude中,Option和他的成员Some和None不需要Option::前缀来使用

    Option标准库定义

    1. enum Option<T> {
    2. Some(T),
    3. None,
    4. }

    基本使用

    1. let some_number = Some(5); //Option<i32>
    2. let some_string = Some("a string"); //Option<&str>
    3. let none:Option<i32> = None; //使用None的时候需要告诉Rust类型,因为只通过None无法推断Some保存的类型

    类型安全示例

  • Option 和 i8 类型不同!!!太棒了!!!

    • i8总会有一个有效的值的,而Option可能没有值,于是我代码里就不需要假设非空值的情况
    • 阻止了空值的泛滥!!!只要目标值不是Option,我就可以安全地认定它的值不会是空
    • 小细节:编译器提示是,相应的Add trait没有实现
      1. let x: i8 = 5;
      2. let y:Option<i8> = Some(5);
      3. let sum = x+y; //错误
  • 但是程序员想要Option时,也不得不写出处理每种可能性的代码,毕竟no free lunch

3、match 控制流 运算符

match的力量来源于模式表现力 和 编译器检查,确保所有可能的情况都得到处理

对比match和if

对于if,表达式必须返回一个bool
match可以是任意类型的

基本内容

match执行是按分支顺序与每一个分支的模式相比较,如果模式匹配了,就执行本分支;否则继续尝试匹配下一个分支
每个分支的结果是:表!达!式!,表达式的结果也会作为match表达式的返回值
分支代码比较短的话可以不用大括号

  1. fn value_in_cents(coin: Coin) -> u8 {
  2. match coin {
  3. Coin::Penny => {
  4. println!("Lucky penny!");
  5. 1
  6. },
  7. Coin::Nickel => 5,
  8. Coin::Dime => 10,
  9. Coin::Quarter => 25,
  10. }
  11. }

匹配Option

比较Option的成员

  1. fn plus_one(x: Option<i32>) -> Option<i32> {
  2. match x {
  3. None => None,
  4. Some(i) => Some(i+1),
  5. }
  6. }
  7. let five = Some(5);
  8. let six = plus_one(five);
  9. let none = plus_one(None);

Rust的匹配是穷尽的

必须穷举到所有可能性来使得代码有效
例如Option中不要忘了处理None的情况
不想列举所有可能性时,可以 _=> ()

  • _ 匹配之前没指定的所有可能值
  • ()是unit值,什么也不会发生
    1. ...
    2. 7 => println!("seven"),
    3. _ => (),

    if let简单控制流

    获得更少的代码,增加简洁度;但失去match强制的穷尽性检查——权衡
    if let可以认为是match的语法糖
    前后对比 ```rust let someu8_value = Some(0u8); match some_u8_value { Some(3) => println!(“three”), => (), }

if let Some(3) = some_u8_value { println!(“three”); } ```