今天是学习rust的第三天,学习材料为官网的《The Rust Programming Language》今日内容包括第六章:枚举enums、第七章:Packages和第八章:Collections
为了避免错误,rust语言没有null值定义,但有一个枚举来实现null的功能——定义在标准库中的Option
enum Option<T> {
Some(T),
None,
}
鉴于其常用性,不需要在使用时显示声明enum Option,同样可以直接使用Some或None而不需要写Option::Some。定义中的
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; //若是使用None则要声明一下你在使用什么数据类型
由于Option
基于代码的运行结果控制程序的走向
enum Coin { //定义一个结构体
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1, //match arm:pattern+code,code的部分可以用{}来组织更为复杂的代码块
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
match与Option
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
*注意枚举类的match必须穷尽所有可能,不然rust会报错!*当我们不想列举所有情况时,想要省略的情况可以用占位符_
来代替,如:
let some_u8_value = 0u8;
match some_u8_value {
1 => println!("one"),
3 => println!("three"),
5 => println!("five"),
7 => println!("seven"),
_ => (), //占位符
}
if let
那么如果我们只想对枚举类型中的某一个情况进行处理时,可以使用if let
if let Some(3) = some_u8_value { //注意此时rust不再有穷尽检查,用户需要自己来保证控制流的正确性!
println!("three");
}
if let同样可以与else搭配使用~
rust提供了多种功能帮助程序媛管理代码,有时称这些工具为module system,包括:
crate:binary或library
crate root:rust编译器开始的源文件
package:提供一系列功能的一个或多个crates,一个package包含一个Cargo.toml
文件,描述了如何build这些crates。
package必须包含0个或1个library crates,可以包含任意数量的binary crates,但是不能为空。
输入命令:
$ cargo new my-project
Created binary (application) `my-project` package
$ ls my-project
Cargo.toml
src
$ ls my-project/src
main.rs
当我们创建一个my-project时,cargo创建了一个Cargo.toml
文件,生成一个package。进入Cargo.toml
文件时,可以看到:
[package]
name = "rust"
version = "0.1.0"
authors = ["MY_NAME"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
注意这里没有提到src/main.rs文件,因为cargo遵循“src/main.rs”为binary crate的crate root,与package有相同的名字。类似的,如果package包含一个src/lib.rs,cargo就知道这个package包含一个library crate,与package邮箱的名字,在编译时会将root file交给rustc
。
一个package可以有多个binary crates,放在src/bin下。
modules帮助我们在crate内组织代码,构成群组,具备一定的可读性并且方便实用。modules还控制项目的privacy,即一个item是否可以被代码外的程序段调用(public)还是仅能够在内部使用(private)。
以一个饭店crate的搭建为例(仅给出结构,没有具体的实现细节)
以下代码:
mod front_of_house { //关键字 mod
mod hosting { //模块内部可以有别的模块
fn add_to_waitlist() {}
fn seat_at_table() {}
}
mod serving {
fn take_order() {}
fn serve_order() {}
fn take_payment() {}
}
}
通过模块,我们可以构建起相关的定义,并且标识他们为什么相关。程序媛可以简单地通过groups去找到想要使用的定义,而不用详细的读所有的定义;添加代码也同样更清晰。
module tree:
crate
└── front_of_house
├── hosting
│ ├── add_to_waitlist
│ └── seat_at_table
└── serving
├── take_order
├── serve_order
└── take_payment
src/main和src/lib作为module tree的根。
与文件系统很像,为了指明模块的位置,我们要给出模块的路径。
self
super
或当前模块中的identifier路径中的间隔使用符号::
module还能帮助我们定义rust的privacy,注意rust中各种items默认是private的。parent节点不能使用child的功能,但是child可以使用其祖先的功能。
pub
Keyword加入 pub
关键字可以将默认private 的item变成public,从而可以访问
super
super关键字相当于文件系统的..
回到上层
对于Structs结构体,当我们将结构体public,其内部变量依然是private的,需要我们逐个进行public,不作操作的内部变量依然会保持private;
对于Enums枚举,我们在enum前添加pub关键字,则整个结构体全部变为public。
use
Keyword使用use关键字,避免代码冗长
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
use crate::front_of_house::hosting; //use关键字,将路径引入
pub fn eat_at_restaurant() {
hosting::add_to_waitlist();
hosting::add_to_waitlist();
hosting::add_to_waitlist();
}
fn main() {}
use
Path通常习惯下,我们将要使用的函数的父节点使用use进行引入,而不是直接引入函数,这样在使用的时候加上::
可以有效地表明这个函数来自别处的定义,不是本地定义。
在使用use时,习惯写明完整的地址。
as
Keywordas重命名,避免重名的尴尬(此处有些像python的import … as …)
pub use
pub use关键字使得我们可以既使用其他代码的模块,又让该模块继续被其他模块调用,所以称为re-exporting。
从crates.io
下载外部依赖,写在 C a r g o . t o m l Cargo.toml Cargo.toml内的[denpendencies]下。代码中通过use
关键字来调用
use
Lists代码
use std::io;
use std::cmp::Ordering;
// ---snip---
与代码
use std::{cmp::Ordering, io};
// ---snip---
等价
使用符号*
来引入所有功能,与python的import * 相同
collections可以容纳多个值,与构建在矩阵中的元组不同,这些collections指向的数据存在堆(heap)中,即元素的长度在编译时是不知道的,可以随着程序的进程而改变。本章内容包括:
使得程序媛可以在一个数据结构中存放多个值,数据在内存中相邻存放。vector中存储的数据必须是相同的类型。具体内容见代码笔记。
在rust的core language中,只定义了一种“string”,为string slice str
,常见为&str
。String 类型是在rust的标准库中提供的,并不在core language中。这两种类型都是UTF-8编码的。
注意String类是不支持下标查询的!这是由于String本身所支持的UTF-8编码导致的。每个UTF-8编码的字符不一定是合法的character。还有一个原因:rust不能保证indexing operation总是在O(1)时间内完成,因为总是要遍历之前的所有元素来确定String中有多少个合法字符。
由于index的方法不能指明返回的到底是a byte value, a character, a grapheme cluster, 还是a string slice,因而是一个不好的操作。rust基于此要求程序媛给出具体的要求,指明要返回的那一部分的切片。
注意此方法要小心给出的切片大小,有可能会出现问题!
HashMap
k为keys的类型,V为值value的类型。通过一个hash函数来决定如何在内存中存放这些key和value。Hash map是的我们可以通过key来查看值
对于普通值例如i32,hash map依然采用copy的策略,原值不会失效;但对于String类型,原值会被move到Hash Map中,原值失效不能再使用。
附上第八章的代码笔记
#![allow(unused_variables)]
/**
This is the code about Vector/String/Hash map
My 3rd day as rustacean
2020.02.17
**/
use std::collections::HashMap;
fn main() {
///vector
let v: Vec <i32> = Vec::new(); //创建新的vector,由于新的vector为空,所以要显式声明其类型
let v = vec![1, 2, 3, 4, 5]; //用此方法创建,rust可以自动识别类型
let mut v= Vec::new();
v.push(5); //push method
v.push(6);
let v = vec![1, 2, 3, 4, 5];
let third = &v[2];
println!("The third element is {}", third); //读取vector中元素的第一种方法:下标法这种方法当下标超过范围时,rust会panic
match v.get(2){ //读取vector中元素的第二种方法:get() 当下标超过范围时,这种方式不会返回panic!!
Some(third) => println!("The third element is {}", third),
None => println!("There is no third element"),
}
let result = v.get(2); //会输出 Some(3)!!!
println!("{:?}",result);
let v = vec![100, 32, 57]; //输出vector中全部的元素
for i in &v {
println!("{}", i);
}
//利用枚举实现一个vector中存储不同类型的元素
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
///String
let mut s = String::new(); //创建string
let data = "initial contents"; //两种使用to_string的方法
let s = data.to_string();
let s = "another example".to_string();
let s = String::from("initial contents"); //创建string
let mut s = String::from("foo"); //updating string
s.push_str("bar"); //push_str 不获取ownership
let mut s = String::from("lo");
s.push('l'); //push方法,可以添加单个char
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; //也可以使用加法进行连接,注意此处的使用,s1已经被move,不可再使用
//连接多个string
let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = format!("{}-{}-{}", s1, s2, s3); //format宏指令,不会接管任何变量的ownership
//注意indexing不适用于string
for c in "नमस्ते".chars() { //打印String 中的每一个元素,调用chars()方法
println!("{}", c);
}
for b in "नमस्ते".bytes() { //调用bytes()方法
println!("{}", b);
}
///Hash Maps
let mut scores = HashMap::new(); //创建HashMap
scores.insert(String::from("Blue"), 50);
scores.insert(String::from("Yellow"), 30);
let teams = vec![String::from("Blue"), String::from("Yellow")];
let initial_scores = vec![10, 50];
//本句中,HashMap<_, _>给rust指定了相应的输出结构。zip创建一个vector,其中元素为tuple,然后使用collect来将其转换为hash map
let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect();
let team_name = String::from("Blue"); //获取Hash Map中的值,使用get方法
let score = scores.get(&team_name);
println!("The score of Blue is {:?}", score); //输出结果为Some(10),因为get()方法会return 一个 Option
for (key, value) in &scores { //另一个输出的方法
println!("{}: {}", key, value);
}
//更新一个hash map
//Overwriting a value
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Blue"), 25);
println!("{:?}", scores); //重复插入相同key的不同值,则旧值会被抛弃
//Only inserting a value if the key has no value
scores.entry(String::from("Yellow")).or_insert(50);
scores.entry(String::from("Blue")).or_insert(50);
println!("{:?}", scores); //只插入不存在的值,之前存在的值不会发生改变
//Updating a value Based on the Old Value
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);
}