《Rust 权威指南》读书笔记,本文将持续跟进
fn main() {
println!("Hello, world!");
}
println!
是一个宏,负责打印。
使用let 关键字进行声明:
let 变量名: 类型;
也可以同时赋初始值:
let 变量名: 类型 = 值;
编译器可对部分值的类型进行推导:
let 变量名 = 值;
i8、i16、i32,i64、i128、isize
u8、u16、u32、u64、u128、usize
isize、usize为平台相关类型
let num: i32 = 100;
debug 模式下变量溢出会panic
release 模式下变量溢出会形成环绕,即最大数+1会变成该类型最小的数。若想显示环绕则使用标准库Wrapping类型。
整数字面量 | 示例 |
---|---|
Decimal | 999_999 |
Hex | 0xff |
Octal | 0o77 |
Binary | 0b1010_0101 |
Byte (u8 only) | b’Z’ |
默认推导类型:i32
f32、f64
let num: f32 = 100.00;
浮点数遵循IEEE-754浮点数标准
默认推导类型:f64
bool
let is_false: bool = false;
char
let ch: char = 'a';
两种内置符合类型
定义:
let tup: (i32, f32, bool) = (1, 2.0, true);
访问:
通过元组名.索引
方式访问
tup.0、tup.1、tup.2
解构:
将元组拆分为多个变量
let (a, b, c) = tup;
定义:
let a = [1, 2, 3, 4, 5, 6];
let a: [i32;6] = [1, 2, 3, 4, 5, 6];
let a: [3;6];
等价于let a = [3, 3, 3, 3, 3, 3];
访问:
通过索引访问
a[0]、a[1]
使用以上方式声明的变量均不可修改,对其赋值会提示编译错误,其体现了变量的不可变性。
error[E0384]: cannot assign twice to immutable variable `a`
--> src\main.rs:31:5
|
30 | let (a,b,c) = tup;
| -
| |
| first assignment to `a`
| help: consider making this binding mutable: `mut a`
31 | a = 100;
| ^^^^^^^ cannot assign twice to immutable variable
若想声明可变的变量则在let
后加 mut
关键字(mut: mutable)。
如:let mut a: i32 = 100;
此时对a赋值a = 200;
是允许的。
同一作用域内可以声明同名变量,下边的变量会隐藏上边的变量。即使类型不同也是可以的。
let a: i32 = 100;
let a: f32 = 100.0;
定义:
const MAX_POINTS: u32 = 100_000;
访问:
可访问,不可修改。
Rust 使用函数使用“蛇形命名”,即小写字母和下划线的组合方式。
无参无返回值形式
fn hello_rust() {
println!("hello, rust!")
}
有参有返回值形式
fn add_one(num :i32) -> i32 {
return num + 1;
}
函数内部也可以定义函数
fn main() {
fn add_one(num :i32) -> i32 {
return num + 1;
}
println!("{}",add_one(1));
}
使用表达式作为函数返回值,不需写return和结尾分号
fn main() {
println!("{}",add_one(1)); // 2
}
fn add_one(num :i32) -> i32 {
num + 1
}
处理多返回值可使用元组
fn f() -> (i32, i64, f32 ) {
(1, 2, 3.0)
}
语句以英文半角下的“;”结尾。
fn main() {
let x = 15;
let y = {
let x = 20 + 1;
x + 5
};
println!("{}",y);
}
x + 6
称之为表达式,x =
后边的20 + 1
也是表达式(有点像数学公式) ,表达式一般用一对{}(表示代码块或作用域)括起来,注意表达式一般写在代码块的最后,其后不写分号,写分号就变成了语句。
// 单行注释,可放在语句末尾,分号后
/* 多行注释放在单行使用 */
/*
* 多行注释
* 多行注释
* 多行注释
*/
/// #这是文档注释
/// ## 可以写一些特殊的文本格式,如markdown
/// ```
/// fn f() -> (i32, i64, f32 ) //返回三个值
/// ```
fn f() -> (i32, i64, f32 ) {
(1, 2, 3.0)
}
if
let a = 3;
if a > 2 {
println!("a > 2");
}
if else if
let a = 3;
if a > 4 {
println!("a > 4");
}else if a > 3{
println!("a > 3");
}else {
println!("what ?");
}
作为表达式使用,表达式中的值必须类型统一。如100、200。不可以为100,“hello”。
let a = 3;
let result = if a > 3 {
100
}else {
200
};
rust 支持3种循环loop,for,while
break 用于打破循环,即跳出当前循环
continue 结束当前层循环
loop
let mut counter = 0;
loop {
counter += 1;
if counter == 100 {
println!("循环了100次");
break;
}
}
作为表达式使用
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // 表达式的值
}
};
println!("The result is {}", result);
while
let mut number = 3;
while number != 0 {
println!("{}", number);
number = number - 1;
}
结束循环,跳转到tag处,常用于打破多重循环
let mut number = 3;
'tag: while number != 0 {
if number == 1 {
break 'tag;
}
println!("{}", number);
number = number - 1;
}
for
迭代数组
iter() 为迭代器
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
正向遍历与反向遍历
1…4 表示范围[1,4),左闭右开,步长为1的从左往右遍历方式
(1…4).rev():反向输出[1,4)的值
for number in 1..4 {
println!("{}", number);
}
for number in (1..4).rev() {
println!("{}", number);
}
Rust使用包含特定规则的所有权系统来管理内存,这套规则允许编译器在编译过程中执行检查工作,而不会产生任何的运行时开销。
Rust中的每一个值都有一个对应的变量作为它的所有者 。
在同一时间内,值有且仅有一个所有者。
当所有者离开自己的作用域时,它持有的值就会被释放掉。
Rust 用“{}”代表作用域。
变量在进入作用域后变得有效。它会保持自己的有效性直到自己离开作用域为止。
{ // 未定义变量s
let s = String::from("hello!"); // 变量s生效
} // 变量s离开作用域,Rust自动调用drop函数,销毁变量,归还内存给操作系统
标量类型,因编译时期就可以明确的知道其占用的内存大小,不会涉及堆内存分配,将其完整的放在栈空间即可。所以b = a
;复制了a 底层的数据3,两个3都在栈空间,相互独立。
let mut a = 3;
let mut b = a;
a += 100;
println!("{}",a); // 103
println!("{}",b); // 3
变量的移动(所有权转移)
以下代码设计到String的底层结构,String是一个既有栈上内存占用,又有堆上内存占用的数据结构,其发生赋值操作s2 = s1
时,会将s1的栈上内容复制一份,给s2,同时将堆上内存的指针转移给s2。s2的出现代替了s1的存在,s1在发生赋值后,便不再可用。这样设计巧妙的避开了同一个内存区域被两次drop。
这其实就是一种浅拷贝操作,但会弃用被拷贝的变量。Rust称上述动作为移动(move
)。Rust永远不会自动地创建数据的深度拷贝。因此在Rust中,任何自动的赋值操作都可以被视为高效的。
let s1 = String::from("hello");
let s2 = s1;
println!("{}",s1);
println!("{}",s2);
error[E0382]: borrow of moved value: `s1`
--> src\main.rs:31:19
|
28 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
29 | let s2 = s1;
| -- value moved here
30 |
31 | println!("{}",s1);
| ^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
深度拷贝,调用其clone()方法
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
trait 类似于其他语言的接口,描述了一系列行为。
Copy trait 负责栈上数据的拷贝工作,实现该trait即可将值复制给其他变量,原变量仍可用。
Drop trait 负责堆内存的释放工作。
同一种类型对于 Copy 和 Drop 只能实现其一。
内置的标量类型整型、浮点型、布尔型、字符型数据都实现了Copy trait。若元组中的所有数据类型都实现了Copy trait,那么该元组也具有被Copy的能力。
完整的存储在栈上的类型都可以实现Copy,只要有堆内存和引用其他资源的就不可以实现Copy,否则编译时报错。
用Copy 或 clone的方式对只保留在栈上的类型的数据进行复制都是可以的。
当函数的参数类型实现了Copy,那么传给函数参数的变量,在调用函数后,仍可使用。反之,则发生了所有权转移,参数的作用域即函数的作用域。在函数执行结束后,会自动调用drop,实现回收。所以,在函数调用处之后,是不可使用因调用函数而发生了所有权转移的变量的。若想继续使用该变量,可将其作为函数的返回值,返回后重新使用。引用可以解决这个壁垒。
Rust 使用 & 代表引用语义,使用 * 代表解引用语义。
引用不持有所有权。
let s1 = String::from("hello");
let s2 = &s1;
println!("{}",s1);
println!("{}",s2);
上述代码s2持有了s1的引用。
引用类型作为函数的参数
fn length(text: &String) -> usize {
text.len()
}
调用上述length函数,不会发生所有权转移,在函数退出时,不销毁传入的参数text。
不可变的引用也称之为借用(borrow)。对length函数中的text
参数进行修改是不被允许的。
作为函数参数,表示为s: &mut String
fn change_string(s: &mut String) {
s.push_str("_修改后的.");
}
let mut s1 = String::from("hello");
change_string(&mut s1);
println!("{}",s1);
以下代码比较难以理解。在使用可变引用时比较迷惑人。
同一时刻,一个变量只能有一个可变引
如下操作不被允许
let mut s1 = String::from("hello");
let mut s2 = &mut s1; //------
s1.push_str("_1"); // | 可变引用s2,被操作时,不能交叉操作s1
s2.push_str("_2"); //--------
error[E0499]: cannot borrow `s1` as mutable more than once at a time
--> src\main.rs:38:5
|
37 | let mut s2 = &mut s1;
| ------- first mutable borrow occurs here
38 | s1.push_str("_1");
| ^^^^^^^^^^^^^^^^^ second mutable borrow occurs here
39 | s2.push_str("_2");
| ----------------- first borrow later used here
如下操作同样不被允许
let mut s1 = String::from("hello");
let mut s2 = &mut s1;
let mut s3 = &mut s1;
s2.push_str("hh");
error[E0499]: cannot borrow `s1` as mutable more than once at a time
--> src\main.rs:38:18
|
37 | let mut s2 = &mut s1;
| ------- first mutable borrow occurs here
38 | let mut s3 = &mut s1;
| ^^^^^^^ second mutable borrow occurs here
39 | s2.push_str("hh");
| ----------------- first borrow later used here
如下操作被允许
let mut s1 = String::from("hello");
let mut s2 = &mut s1;
s2.push_str("_2");
s1.push_str("_1");
使用作用域隔离可变引用
let mut s1 = String::from("hello");
{
let mut s2 = &mut s1;
s2.push_str("hh");
}
let mut s3 = &mut s1;
println!("{}",s3);
println!("{}",s1);
发生数据竞争的条件:
1.两个或两个以上的指针同时访问同一空间。
2.其中至少有一个指针会向空间中写入数据。
3.没有同步数据访问的机制。
操作可变引用后,不可以操作原来的不可变引用
允许的
let mut s1 = String::from("hello");
let r1 = &s1;
let r2 = &s1;
println!("{}-{}",r1,r2);
let r3 = &mut s1;
println!("{}",r3);
不允许的
let mut s1 = String::from("hello");
let r1 = &s1;
let r2 = &s1;
println!("{}-{}",r1,r2);
let r3 = &mut s1;
println!("{}",r3);
println!("{}",r1);
error[E0502]: cannot borrow `s1` as mutable because it is also borrowed as immutable
--> src\main.rs:40:14
|
37 | let r1 = &s1;
| --- immutable borrow occurs here
...
40 | let r3 = &mut s1;
| ^^^^^^^ mutable borrow occurs here
41 | println!("{}",r3);
42 | println!("{}",r1);
| -- immutable borrow later used here
未引入生命周期,悬垂引用不被允许
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
error[E0106]: missing lifetime specifier
--> src\main.rs:39:16
|
39 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime
|
39 | fn dangle() -> &'static String {
| +++++++
在任何一段给定的时间里,要么只能拥有一个可变引用,要么只能拥有任意数量的不可变引用,引用总是有效的。
切片是一种特殊的引用形式。其不持有所有权。
切片允许我们引用集合中某一段连续的元素序列。
将整个集合转换成切片
let slice = 集合[..];
将集合一部分转换成切片
let slice = 集合[1..];// 从索引1到集合最后
let slice = 集合[1..2];// 从索引1到索引2
let slice = 集合[..2];// 从索引0到索引2
let slice = 集合[0..3];// 从索引0到索引3
切片中[start…end],start与end构成的是一个左闭右开的区间[start,end)
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
println!("{}",hello);
println!("{}",world);
对字符串切片时需考虑字符边界问题,UTF-8编码使用1-4字节表示一个字符。
字符串字面量就是切片。
let s = "hello, world";
let s :&str = "hello world";
str类型即字符串切片类型。
定义方式
struct 结构体名 {
字段名0:类型,
字段名1:类型,
}
访问方式
对象.字段名
定义User结构体
struct User {
name: String,
age: i32,
}
使用User结构体
let user= User{
name: "ophelia".to_string(),
age:13,
};
println!("{:?}",user.name);
println!("{:?}",user.age);
用函数方式创建User对象
fn new_user(name: String, age: i32) -> User {
User{
name:name,
age: age,
}
}
若函数形参名与结构体中字段重合,则不用写结构体的字段名,直接写入形参
fn new_user_two(name: String, age: i32) -> User {
User{
name,
age,
}
}
let mut user = new_user(String::from("张三"),20);
user.age = 22;
println!("{}",user.age);
let user = new_user_two(String::from("李四"),20);
println!("{}",user.name);
使用已有的结构体中的字段
方式1
let user = new_user_two(String::from("李四"),20);
//使用其他user中的字段创建user
let user1 = User{
name:String::from("judy"),
age:user.age,
};
方式2 …运算符。语法糖。
let user = new_user_two(String::from("李四"),20);
//使用其他user中的字段创建user
let user1 = User{
name:String::from("julia"),
..user
};
定义方式
struct 结构体名(类型1,类型2,类型3);
访问方式
对象.索引
定义元组结构体
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
println!("{}",black.0);
定义方式
struct 结构体名 {}
空结构体一般用作实现trait。实现trait时,不需要使用结构体中的数据。
空结构体与空元组类似 let tup = ();
若结构体中所有字段都是非引用类型,那么只要结构体携带的数据时有效的,那么结构体就是有效的若结构体中有字段为引用类型,则需要引入生命周期的概念。
不在结构体上添加 #[derive(Debug)]
是无法实现打印功能的。
还需要修改打印宏的占位符的参数 println!("{:?}",user);
加上:?
。
#[derive(Debug)]
struct User {
name: String,
age: i32,
}
let user= User{
name: "ophelia".to_string(),
age:13,
};
println!("{:?}",user); // User { name: "ophelia", age: 13 }
方法与函数类似:都使用fn关键字及一个名称来进行声明;它们都可以拥有参数和返回值;另外,它们都包含了一段在调用时执行的代码。但是,方法与函数依然是两个不同的概念,因为方法总是被定义在某个结构体(或者枚举类型、trait对象)的上下文中,并且它们的第一个参数永远都是self,用于指代调用该方法的结构体实例。
定义方式
impl 结构体名 {
fn 方法名0(self,参数1:类型) -> 返回值类型 {
返回值
}
fn 方法名1(&self,参数1:类型,参数2:类型) -> 返回值类型 {
返回值
}
fn 方法名2(&mut self,参数1:类型) -> 返回值类型 {
返回值
}
fn 函数名(参数1:类型)-> 返回值类型 {
返回值
}
}
代码
#[derive(Debug)]
struct User {
name: String,
age: i32,
}
impl User {
fn say(&self) {
println!("say");
}
fn println(&mut self) {
self.name = String::from("张三");
println!("{:?}",self);
}
fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器
User{
name,
age,
}
}
}
fn main() {
let mut user = User{
name:String::from("judy"),
age: 16
};
let mut user = User::new_user(String::from("judy"),15);
user.say();
user.println();
}
为结构体绑定函数,但是函数参数不传入self。则称之为关联函数,关联函数常用于创建对象。
访问方式一般为: let 变量名 = 结构体::关联函数名(参数);
impl User {
fn say(&self) {
println!("say");
}
fn println(&mut self) {
self.name = String::from("张三");
println!("{:?}",self);
}
fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器
User{
name,
age,
}
}
}
Rust 支持将实现同一类型的方法,拆分到多个impl块中
impl User {
fn get_name(self) ->String {
self.name
}
}
impl User {
fn get_age(self) ->i32 {
self.age
}
}
定义
这里的变体
就是枚举值
enum 枚举名 {
变体1,
变体2(类型),
}
访问
枚举名::变体名(参数)
或
变体名(参数)
代码
枚举中支持匿名结构体及空结构体struct Name;
#[derive(Debug)]
enum Paper {
White(String),
Black,
Blue {x:i32,y:i32},
Pink(i32,i32,i32)
}
fn main() {
let p = Paper::Black;
println!("{:?}",p);
println!("{:?}",Paper::White(String::from("白色")));
}
可以将枚举类型加入结构体中。
也可为枚举类型绑定方法,与结构体绑定方法大同小异。
该枚举可以表示非空值与空值。他有Some(T),和None两个变体。Some表示值非空,None表示值为空
let str1 = Some("hello");
let str1 = None;
match允许将一个值与一系列的模式相比较,并根据匹配的模式执行相应代码。模式可由字面量、变量名、通配符和许多其他东西组成。
let op :Option<i32> = Some(128);
match op {
Some(op) => {
println!("有值")
},
None => {
println!("无值")
}
}
作为表达式使用
_ 代表通配符
_ => {} 为默认匹配模式,只有一条表达式时,{}可不写
let value = 100;
let result = match value {
10 => {
10 + 2
},
90 => {
90 + 3
}
_ => 0
};
println!("{}",result); // 0
包与模块的划分是为了更好的管理、维护、复用代码。
包:一个用于构建、测试并分享单元包的Cargo功能。
一个包(package)可以拥有多个二进制单元包及一个可选的库单元包。
单元包:一个用于生成库或可执行文件的树形模块结构。
可将部分代码拆分到独立的单元包(crate)中,并将它作为外部依赖进行
引用。
cargo new Rust
创建项目Rust
项目结构
Rust
|-------src
| |----------main.rs
|-------target
| |----------debug
| |------------一系列目录与文件,包括生成的可执行文件
|--------Cargo.lock
|--------Cargo.toml
Cargo会默认将src/main.rs 视作一个二进制单元包的根节点,这个二进制单元包与包拥有相同的名称。
它们被用于控制文件结构、作用域及路径的私有性。
通过定义模块来控制作用域及私有性:
mod 模块名 {
// 代码...
}
pub mod 模块名 {
// 代码...
}
模块中可以嵌套的定义模块。
通过使用模块,可以将相关的定义分到一组,并根据它们的关系指定有意义的名称。
一种用于命名条目的方法,这些条目包括结构体、函数和模块等。
用于在模块树中指明条目的路径:
路径有两种形式:
1.使用单元包名或字面量crate从根节点开始的绝对路径。
2.使用self、super或内部标识符从当前模块开始的相对路径。
绝对路径与相对路径都由至少一个标识符组成,标识符之间使用双冒号(::)分隔。
Rust中的所有条目(函数、方法、结构体、枚举、模块及常量)默认都是私有的。
使用pub关键字来暴露路径:
pub mod user_mod {
pub mod action_mod {
pub fn Do(){
println!("do something!")
}
}
#[derive(Debug)]
pub struct User {
name: String,
age: i32,
}
impl User {
fn say(&self) {
println!("say");
}
pub fn println(self) {
println!("{:?}",self);
}
pub fn new_user(name:String, age:i32) ->User { // 关联函数,第一个参数非self,常用于结构体的构造器
User{
name,
age,
}
}
fn get_name(self) ->String {
self.name
}
fn get_age(self) ->i32 {
self.age
}
}
}
使用super关键字开始构造相对路径
fn hello() {
println!("hello!")
}
pub mod test {
fn hello() {
super::hello();
}
}
将结构体或枚举声明为公共的
pub mod test_one {
pub struct Person {
pub name: String, // 需逐个公开
age: i32,
}
pub enum Action { //枚举只要公开一个成员,即公开了所有成员
Song,
Jump,
Rap,
}
}
用use关键字将路径导入作用域
未使用use之前
fn main() {
//绝对路径
let user = crate::user_mod::User::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
//相对路径
let user = user_mod::User::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
}
使用use之后
use crate::user_mod::User;
fn main() {
//绝对路径
let user = User::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
//相对路径
let user = User::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
}
使用as关键字来提供新的名称
use crate::user_mod::User as U;
fn main() {
//绝对路径
let user = U::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
//相对路径
let user = U::new_user(String::from("张三"),12);
user.println(); // User { name: "张三", age: 12 }
}
使用pub use重导出名称
不仅将条目引入了作用域,而且使该条目可以被外部代码从新的作用域引入自己的作用域。
pub use crate::user_mod::User;
其他包在导入该包后,可访问该包因如的User。
使用外部包
更新Cargo.toml文件
Cargo.toml
[dependencies]
rand = "0.5.5"
在dependencies下指定包名,版本号,用use 导入即可使用。
使用嵌套的路径来清理众多use语句
use std::cmp::Ordering;
use std::io;
use std::{cmp::Ordering, io};
通配符*
use std::collections::*;
将模块拆分为不同的文件
原文件
// src/lib.rs
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
拆分后的文件
// src/lib.rs
mod front_of_house; // 声明front_of_house模块,让Rust前往与当前模块同名的文件中加载模块内容。
pub use crate::front_of_house::hosting;
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
// src/front_of_house.rs
pub mod hosting;
// src/front_of_house/hosting.rs
pub fn add_to_waitlist() {}
该特性与Cargo相关,后续介绍
在编写代码的嵌套上下文中有一系列被定义在“作用域内”的名字。可以创建作用域并决定某个名字是否处于该作用域中,但是不能在同一作用域中使用相同的名字指向两个不同的条目;有一些工具可以被用来解决命名冲突。
动态数组(vector)可以让你连续地存储任意多个值。数据存储在堆上。
创建
let v: Vec
使用宏创建并赋初值
let v = vec![1, 2, 3];
插入
v.push(5);
v.push(6);
v.push(7);
v.push(8);
读取
let v = vec![1, 2, 3, 4, 5];
let third: &i32 = &v[2];
println!("The third element is {}", third);
match v.get(2) {
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element."),
}
遍历
let v = vec![100, 32, 57];
for i in &v {
println!("{}", i);
}
let mut v = vec![100, 32, 57];
for i in &mut v {
*i += 50;
}
使用枚举存储多个类型
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
字符串(string)是字符的集合。&str 是字符串切片。Rust的标准库中同时包含了其他一系列的字符串类型,比如OsString、OsStr、CString及CStr。
创建
let mut s = String::new();
创建字面量
let data = "initial contents";
let s = data.to_string();
从字面量生成
let s = String::from("initial contents");
更新 push_str 或 push
let mut s = String::from("foo");
s.push_str("bar");
s.push('a');
遍历
for c in "你好,world!".chars() {
println!("{}", c);
}
for c in "你好,world!".bytes() {
println!("{}", c);
}
索引
Rust 字符串使用UTF-8字符编码,字符长度不固定。不支持索引。
切片
做切片时应充分考虑边界问题。
哈希映射(hash map)可以让你将值关联到一个特定的键上,
它是另外一种数据结构—映射 (map)的特殊实现。数据存储在堆上。
创建
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
哈希映射与所有权
一旦键值对被插入,其所有权就会转移给哈希映射。假如我们只是将值的引用插入哈希映射,那么这些值是不会被移动到哈希映射中的。
访问哈希映射中的值
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
let team_name = String::from("Blue");
let score = scores.get(&team_name);
更新哈希映射,旧值覆盖
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores);
只在键没有对应值时插入数据
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores);
基于旧值来更新值
use std::collections::HashMap;
let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
let count = map.entry(word).or_insert(0);
*count += 1;
}
println!("{:?}", map);