本文大部分内容翻译自:The Rust Programming Language
1. Defining and Instantiating Structs
struct中可以包含不同类型元素(跟tuple一样)
每个元素有自己的名字(与tuple不同)- 所以struct在访问元素时比tuple灵活
1.1. 定义结构体 1 2 3 4 5 6 struct User { active: bool , username: String , email: String , sign_in_count: u64 , }
里面的元素叫做“fields”。
1.2. 使用结构体 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 , }; }
用.
获得结构体中的“field”:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct User { active: bool , username: String , email: String , sign_in_count: u64 , } fn main () { let mut user1 = User { email: String ::from ("someone@example.com" ), username: String ::from ("someusername123" ), active: true , sign_in_count: 1 , }; user1.email = String ::from ("anotheremail@example.com" ); }
整个结构体实例必须是可变的,不允许只让一部分“fields”是可变的。
1.3. 字段初始化简写语法 当用于初始化的变量名与“fields”的名字相同时,可以省略“fields”的名字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 struct User { active: bool , username: String , email: String , sign_in_count: u64 , } fn build_user (email: String , username: String ) -> User { User { email, username, active: true , sign_in_count: 1 , } } fn main () { let user1 = build_user ( String ::from ("someone@example.com" ), String ::from ("someusername123" ), ); }
1.4. 用另一个实例来初始化新实例 使用user1
初始化user2
: 麻烦的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 { active: user1.active, username: user1.username, email: String ::from ("another@example.com" ), sign_in_count: user1.sign_in_count, }; }
使用结构体的update语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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" ), ..user1 }; }
..
表示除了明确指出的fields之外,其它fields跟user1
都相同。user1
必须最后出现。
这样创建user2
之后,不能再使用user1
了,因为user1
中的username
是一个String
类型,执行的是移动(move)操作,即username
被moved 到了user2
。
但如果将email
和username
都赋予新String
值,只使用user1
的active
和sign_in_count
,那么还可以继续使用user1
,因为active
和sign_in_count
是布尔值和整数,使用的是Copy
而不是move
。
1.5. 元组结构体(Tuple Structs) 1 2 3 4 5 6 7 struct Color (i32 , i32 , i32 ); struct Point (i32 , i32 , i32 ); fn main () { let black = Color (0 , 0 , 0 ); let origin = Point (0 , 0 , 0 ); }
Rust还支持看上去类似于元组的结构体,称为元组结构体(Tuple Structs)。元组结构体具有结构体名称提供的附加含义,但没有与其字段关联的名称,它们只有字段的类型。当你想给整个元组命名 并使该元组与其他元组类型不同,而且给每个字段命名会显得冗余时,元组结构体非常有用。
1.6. Unit-like structs 没有任何fields的结构体——unit-like structs。
Unit-like structs can be useful when you need to implement a trait on some type but don’t have any data that you want to store in the type itself.
例:
1 2 3 4 5 struct AlwaysEqual ;fn main () { let subject = AlwaysEqual; }
2. An Example Program Using Structs 1 println! ("rect1 is {:?}" , rect1);
Putting the specifier :?
inside the curly brackets tells println! we want to use an output format called Debug
. The Debug
trait enables us to print our struct in a way that is useful for developers so we can see its value while we’re debugging our code.
会有编译错误:
1 error[E0277]: `Rectangle` doesn't implement `Debug`
需要在结构体定义之前添加outer attribute #[derive(Debug)]
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } fn main () { let rect1 = Rectangle { width: 30 , height: 50 , }; println! ("rect1 is {:?}" , rect1); }
输出:
1 2 3 4 5 $ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished dev [unoptimized + debuginfo] target(s) in 0.48s Running `target/debug/rectangles` rect1 is Rectangle { width: 30, height: 50 }
如果使用{:#?}
,会输出:
1 2 3 4 5 6 7 8 $ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished dev [unoptimized + debuginfo] target(s) in 0.48s Running `target/debug/rectangles` rect1 is Rectangle { width: 30, height: 50, }
使用dbg!
macro:
打印到标准错误控制台流(stderr)
接收一个表达式的所有权,打印出代码中调用dbg!
宏时所在的文件和行号,以及该表达式的结果值,并返回该值的所有权
Here’s an example where we’re interested in the value that gets assigned to the width field
, as well as the value of the whole struct in rect1
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } fn main () { let scale = 2 ; let rect1 = Rectangle { width: dbg!(30 * scale), height: 50 , }; dbg!(&rect1); }
输出结果:
1 2 3 4 5 6 7 8 9 $ cargo run Compiling rectangles v0.1.0 (file:///projects/rectangles) Finished dev [unoptimized + debuginfo] target(s) in 0.61s Running `target/debug/rectangles` [src/main.rs:10] 30 * scale = 60 [src/main.rs:14] &rect1 = Rectangle { width: 60, height: 50, }
The dbg!
macro can be really helpful when you’re trying to figure out what your code is doing!
3. 结构体的方法(Method) 方法(method)与函数(function)类似: - 用fn
关键字和名称声明,可以拥有参数和返回值,同时包含在某处调用该方法时会执行的代码。
方法与函数不同之处:
方法在结构体的上下文中被定义(或枚举、trait对象的上下文)
第一个参数总是self
,代表调用该方法的结构体实例
3.1. 定义方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #[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 () ); }
定义Rectangle
结构体的area
方法:
start an impl
(implementation) block for Rectangle
Everything within this impl block will be associated with the Rectangle
type.
将area
移到impl
花括号里面
将area
第1个参数改为self
&self
是self: &Self
的简写,&
表示我们借用(borrow)这个Rectangle
实例
方法的名字可以和field的名字相同。
3.2. 关联函数(Associated Functions) 所有在impl
块中定义的函数被称为关联函数(associated functions) ,因为它们与impl
后面命名的类型相关。
我们可以定义不以self
为第一参数的关联函数(因此这个关联函数不是方法),因为它们并不作用于一个结构体的实例。我们已经使用了一个这样的函数:在String类型上定义的String::from
函数。
不是方法的关联函数经常被用作返回一个结构体新实例的构造函数 。
例如:我们可以提供一个关联函数,它接受一个维度参数并且同时作为宽和高,这样可以更轻松的创建一个正方形Rectangle
,而不必指定两次同样的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } impl Rectangle { fn square (size: u32 ) -> Rectangle { Rectangle { width: size, height: size, } } } fn main () { let sq = Rectangle::square (3 ); }
使用结构体名和::
语法来调用这个关联函数:
1 let sq = Rectangle::square (3 );
这个函数位于结构体的命名空间中:::
语法用于关联函数和模块创建的命名空间。
3.4. 多个impl
块 每个结构体都允许拥有多个 impl 块。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #[derive(Debug)] struct Rectangle { width: u32 , height: u32 , } 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 } } 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)); }
参考资料 [1] Using Structs to Structure Related Data:https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html