结构体 struct

定义和实例化

#![allow(unused)]
fn main() {
struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}
}

上面定义了一个user结构体

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

    user1.email = String::from("anotheremail@example.com");
}

上面创建了一个user的可变实例user1,注意整个实例都是可变的,rust不支持控制指定字段可变性

初始化时并不需要按照struct字段的顺序

创建实例时可省略字段名

#![allow(unused)]
fn main() {
fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}
}

使用更新语法 struct update syntax

fn main() {
    // --snip--

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    };
    //下面的struct update syntax比上面的简洁许多
    let user2 = User {
        email: String::from("another@example.com"),
        ..user1
    };
}

注意 stuct update syntax类似于使用 =赋值。上述的 user1name所有权已经 moveduser2了。不能再次使用user1name。但activesign_in_count是实现copy存储在stack上的,所以可以再次使用。

元组结构体 Tuple Structs

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

fn main() {
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}

注意 blackorigin是不同类型,虽然他们的字段类型相同

元组结构体的字段没有名字,类似于元组可通过索引访问

类似于空元组()unit的结构体

空结构体在你需要实现某些特性trait,但不想存储任何数据的时候很实用

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

使用派生特性添加实用功能 Adding Useful Functionality with Derived Traits

struct Rectangle {
    width: u32,
    height: u32,
}

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

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

使用println! 宏以{}打印结构体时,需要结构体实现Display trait

使用{:?}打印结构体时,需要结构体实现Debug trait

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

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

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

以上将输出

rect1 is Rectangle { width: 30, height: 50 }

使用{:#?}将输出

#![allow(unused)]
fn main() {
rect1 is Rectangle {
    width: 30,
    height: 50,
}
}

另外一种方式是使用dbg!宏。dbg!宏将输出到stderr,而println!宏则输出到stdoutdbg!宏将会改变参数的所有权,println!宏是borowing参数的引用

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

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

    dbg!(&rect1);
}

输出

#![allow(unused)]
fn main() {
[src/main.rs:10:16] 30 * scale = 60
[src/main.rs:14:5] &rect1 = Rectangle {
    width: 60,
    height: 50,
}
}

方法 Method

方法method大体类似函数function,但method的第一个参数始终是self

定义方法

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

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

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

    println!(
        "The area of the rectangle is {} square pixels.",
        rect1.area()
    );
}

其中&serfself:&Self的语法糖,在impl块中Self就是这个块的类型别名。方法可以拿走所有权(使用self),也可以借用(&self)和可变借用(&mut self)

注意方法名可以和字段名相同

impl Rectangle {
    fn width(&self) -> bool {
        self.width > 0
    }
}

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

    if rect1.width() {
        println!("The rectangle has a nonzero width; it is {}", rect1.width);
    }
}

使用同名方法返回字段在其它语言叫getter

调用方法直接使用struct的实例即可,rust会自动处理和方法签名的Receiver匹配

方法可以有参数

#![allow(unused)]
fn main() {
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}
}

can_hold方法除了一个不可变借用的Receiver外还有一个同类型的不可变借用参数

相关函数 Associated Functions

所有定义在impl块中的函数都被称为associated functions

在某些不需要类型实例的清下可以定义第一个参数不是self的的函数(因为没有self,所以不是方法)

不是方法的associated function经常被用来作为返回该类型新实例的构造器;如常用的String::from就是String类型的构造器

#![allow(unused)]
fn main() {
impl Rectangle {
    fn square(size: u32) -> Self {
        Self {
            width: size,
            height: size,
        }
    }
}
}

如上面的square函数通过Self(该类型别名)方便的构造一个正方形Rectangle实例

调用associated function语法为Rectangle::square(3);

::语法同时用在associated function和modules创建的命名空间上

多个impl

#![allow(unused)]
fn main() {
impl Rectangle {
    fn area(&self) -> u32 {
        self.width * self.height
    }
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}
}

总结

  1. struct让你可以自定义类型用于存储多个相关联的数据
  2. impl可以定义和类型相关的函数(associated function),方法(method)也是一种让你可以指定struct实例行为的相关函数