Rust结构体
结构体
struct,或者 structure,是一个自定义数据类型,允许你命名和包装多个相关的值,从而形成一个有意义的组合。如果你熟悉一门面向对象语言,struct 就像对象中的数据属性。结构体和我们在第三章讨论过的元组类似。和元组一样,结构体的每一部分可以是不同类型。但不同于元组,结构体需要命名各部分数据以便能清楚的表明其值的意义。由于有了这名字,结构体比元组更灵活:不需要依赖顺序来指定或访问实例中的值。也就是说元组和结构体类似,但是结构体有具体的代号
结构体的声明和实例化
#[derive(Debug)] // 必须带有这个 // 声明结构体 struct User { username: String, email: String, active: bool, } fn main() { // 实例化 let mut user1 = User { // 使用mut可以修改里面的属性 email: String::from("someone@qq.com"), username: String::from("wang"), active: true, }; user1.email = String::from("666@qq.com"); println!("{:?}", user1); }
结构体的实例构造函数
为了方便,我们可以写一个函数专门用来返回新实例化的对象,以及字段名和变量名一致可以简写,这里我们比如说是builder_user可以如下:
#[derive(Debug)] // 必须带有这个 // 声明结构体 struct User { username: String, email: String, active: bool, } fn build_user(email: String, username: String) -> User { User { // 使用mut可以修改里面的属性 email: email, username, // 一致就简写 active: true, } } fn main() { let mut user1 = build_user(String::from("xxx"), String::from("erwqrq")); user1.email = String::from("666@qq.com"); println!("{:?}", user1); }
从另一个实例来更新新实例
调用另一个实例的属性值来创建新实例,代码如下:
fn main() { let user1 = User { email: String::from("xxxx"), username: String::from("wang"), active: false, }; let user2 = User { email: String::from("455"), username: user1.username, active: false, }; let user3 = User { email:String::from("2232"), ..user2 // 支持结构赋值 }; println!("{:?}", user3); }
使用没有命名字段的元组结构体来创建不同的类型
struct Color(i32, i32, i32); struct Point(i32, i32, i32); fn main() { let black = Color(0,0,0); let origin = Point(0,0,0); }
结构体数据所有权
User 结构体的定义中,我们使用了自身拥有所有权的 String 类型而不是 &str 字符串 slice 类型。这是一个有意而为之的选择,因为我们想要这个结构体拥有它所有的数据,为此只要整个结构体是有效的话其数据也是有效的。生命周期确保结构体引用的数据有效性跟结构体本身保持一致。如果你尝试在结构体中存储一个引用而不指定生命周期将是无效的,比如这样:
// 声明结构体 struct User { username: &str, email: &str, // 定义的时候就报错了,提示没有生命周期 active: bool, }
使用结构体创建一个demo:
// 声明结构体 fn area(width: u32, height: u32) -> u32 { width * height } fn area1(dimensions: (u32, u32)) -> u32 { dimensions.0 * dimensions.1 } fn area2(rectangle: &Rectangle) -> u32 { rectangle.width * rectangle.height } // 结构体 struct Rectangle { width: u32, height: u32, } fn main() { // 最初版本 let width = 30; let height = 50; println!("The area value :{}", area(width, height)); // 元组重构 let rect1 = (30, 50); println!("The area value :{}", area1(rect1)); let rect2 = Rectangle { width: 32, height: 55, }; println!("The area value :{}", area2(&rect2)); }
输出结构体实例
由于Rust自带的println!不能打印出实例,所以我们需要在{}中添加#?来打印,同时需要在文件头部添加:#[derive(Debug)],在 {} 中加入 :? 指示符告诉 println! 我们想要使用叫做 Debug 的输出格式。 Debug 是一个 trait,它允许我们以一种对开发者有帮助的方式打印结构体,以便当我们调试代码时能看到它的值。
结构体方法实现
例子中,使用 &self 来替代 rectangle: &Rectangle ,因为该方法位于 implRectangle 上下文中所以 Rust 知道 self 的类型是 Rectangle 。注意仍然需要在 self 前面加上 & ,就像 &Rectangle 一样。方法可以选择获取 self 的所有权,或者像我们这里一样不可变地借用 self ,或者可变地借用 self ,就跟其他参数一样。这里选择 &self 的理由跟在函数版本中使用 &Rectangle 是相同的:我们并不想获取所有权,只希望能够读取结构体中的数据,而不是写入。如果想要在方法中改变调用方法的实例,需要将第一个参数改为 &mut self 。通过仅仅使用 self 作为第一个参数来使方法获取实例的所有权是很少见的;这种技术通常用在当方法将 self 转换成别的实例的时候,这时我们想要防止调用者在转换之后使用原始的实例。
实例方法的调用:
在 C/C++ 语言中,有两个不同的运算符来调用方法: . 直接在对象上调用方法,而 -> 在一个对象的指针上调用方法,这时需要先解引用(dereference)指针。换句话说,
#[derive(Debug)] struct Point { x: f64, y: f64, } impl Point { fn distance(&self, other: &Point) -> f64 { let x_squared = f64::powi(other.x - self.x, 2); let y_squared = f64::powi(other.y - self.y, 2); f64::sqrt(x_squared + y_squared) } } fn main() { let p1 = Point { x: 0.0, y: 0.0 }; let p2 = Point { x: 5.0, y: 6.5 }; println!("{}", p1.distance(&p2)); println!("{}", (&p1).distance(&p2)); }
#[derive(Debug)] // 必须带有这个 struct Rectangle { width: u32, height: u32, } 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 } } fn main() { let rect1 = Rectangle { width: 30, height: 50, }; let rect2 = Rectangle { width: 10, height: 40, }; let rect3 = Rectangle { width: 60, height: 45, }; println!("Can rect1 hold rect2? {}", rect1.can_hold(&rect2)); println!("Can rect1 hold rect3? {}", rect1.can_hold(&rect3)); }
关联函数
impl 块的另一个有用的功能是:允许在 impl 块中定义 不 以 self 作为参数的函数。这被称为 关联函数(associated functions),因为它们与结构体相关联。它们仍是函数而不是
#[derive(Debug)] // 必须带有这个 struct Rectangle { width: u32, height: u32, } impl Rectangle { fn area(&self) -> u32 { self.width * self.height } fn square(size: u32) -> Rectangle { Rectangle { width: size, height: size, } } } fn main() { println!("Can rect1 area {}", Rectangle::square(30).area()); }
多个impl块
每个结构体都允许拥有多个 impl 块。每个方法有其自己的 impl 块。