学习 rust 基础


microsoft 篇

基本程序结构

Rust 函数

每个 rust 程序必须有名为main的函数

fn main() {
  printIn!("Hello, world");
}

代码缩进

大多语句以;结尾。缩进四个空格。

todo! 宏 (macro)

Rust 中的宏类似于采用可变数量的输入参数的函数。

fn main() {
  todo!("Display the message by using the println!() macro");
}

println! 宏

println!宏需要一个或多个输入参数,这些参数会显示在屏幕或标准输出中。

fn main() {
  // println! displays the input 'Hello, world' to the screen
  println!("Hello, world!");
}

参数的值替换

fn main() {
  println!("The first letter of the English alphabet is {} and the last letter is {}.", 'A', 'Z');
}

Rust 中创建和使用变量

变量

声明变量后,可将其绑定到某个值,也可稍后在程序中绑定该值。

let a_number = 10;

如果尝试在绑定a_number之前显示该变量值,则编译器会返回错误。

不可变与可变

变量绑定默认不可变。 更改值必须先使用mut关键字将变量绑定为可变。

let mut a_number = 10;
println!("The number is {}.", a_number);

a_number = 15;
println!("Now the number is {}.", a_number);

变量隐藏

新的声明会创建新的绑定。在 Rust 中,此操作称为”隐藏”,旧变量仍在,但无法再于此范围内引用它。

探索数字、文本和 true/false 值的数据类型

Rust 是一种静态类型化的语言。编译器通常可以根据绑定值推断变量数据类型。多种类型必须通过使用类型注释让编译器得知特定类型。

let number: u32 = "14"
// 编译器会发生错误

内置数据类型

数字:整数和浮点数
长度有符号无符号
8 位i8u8
与体系结构相关isizeusize

isize uszie 类型取决于运行程序的计算机类型。 64 位系统使用 64 位类型。 默认浮点数f64

布尔值:True 或 false

bool 类型有两个可能值:truefalse

文本:字符或字符串

字符 char类型是最基元的文本类型。

let uppercase_s = 'S';
let lowercase_f = 'f';
let smiley_face = '😀';

字符串 str 类型也称为”字符串切片”, 它是字符串数据的一种视图。字符串字面量的类型都是&str
对于字符串未知 如用户输入等场景,Rust 具有另一个名为String的字符串类型。此类型在堆上分配。

// 使用:char 注释语法声明字符变量
let character_1: char = 'S';
let character_2: char = 'f';

// 通过编译器推断数据类型
let smiley_face = '😀';

//
let string_1 = "miley ";

let string_2: &str = "ace";

元组和结构定义数据集合

元组

let tuple_e = ('E', 5i32, true);

println!("Is '{}' the {}th letter of the alphabet? {}", tuple_e.0, tuple_e.1, tuple_e.2);

结构

Rust 支持三种结构类型:经典结构、元组结构和单元结构。

// 经典 C 结构 最常用
struct Student { name: String, level: u8, remote: bool }

// 元组
struct Grades(char, char, char, char, f32);

// 单元结构最常用作标记。 
struct Unit;

实例化结构

let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 };
let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };

let mark_1 = Grades('A', 'A', 'B', 'A', 3.75);
let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);

println!("{}, level {}. Remote: {}. Grades: {}, {}, {}, {}. Average: {}", user_1.name, user_1.level, user_1.remote, mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4);

字符串文本转为 String 类型

编译器建议使用.to_string()函数进行转换。

为复合数据使用枚举变量

Rust 中提及的枚举通常称为代数数据类型。

enum WebEvent {
  // 没有关联的数据类型或数据
  WELoad,
  // 具有两个数据类型分别为 String 和 char 的字段
  WEKeys(String, char),
  // 包含命名字段为 x 和 y 以及字段的数据类型为 i64 的匿名结构。 
  WEClick { x: i64, y: i64 }
}

实例化枚举

struct KeyPress(String, char);
struct MouseClick { x: i64, y: i64 };

enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress) };

// 简单变体
let we_load = WebEvent::WELoad(true);

// 结构变体
let click = MouseClick { x: 100, y: 250 };
let we_click = WebEvent::WEClick(click);

// 元组变量
let keys = KeyPress(String::from("Ctrl+"), 'N');
let we_key = WebEvent::WEKeys(keys);

通过 #[derive(Debug)] 语法可以在代码执行期间查看某些在标准输出中无法查看的值。

Rust 中使用函数

Rust 中函数定义以fn关键字开头。

fn main() {
  println!("Hello, world");

  let formal = "Formal: Goodbye.";
  let casual = "Casual: See you later!";
  goodbye(formal);
  goodbye(casual);
}

fn goodbye(message: &str) {
  println!("\n{}", message);
}

返回值

-> <type>

fn divide_by_5(num: u32) -> u32 {
  if num == 0 {
    // 显式使用 return 关键字提前从函数返回
    return 0;
  }
  num / 5
}

查看签名

函数声明的第一部分称为函数签名。

使用 Rust 中的 if/else 表达式测试条件

创建和使用数组

// 未指定长度的逗号分隔的值列表
let days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];

// 初始值后跟一个分号,然后是数组长度
let bytes = [0; 5];

编译时,数组签名[T; size].

如果尝试使用不在允许范围内的索引访问数组中的元素,编译器将返回错误。

浏览矢量数据类型

矢量存储数据类型相同的多个值,向量的大小或长度可以随时增大或减小。
声明和初始化向量的常用方法是使用vec!宏。

let three_nums = vec![15, 3, 46];
println!("Initial vector: {:?}", three_nums);

let zeroes = vec![0; 5];
println!("Zeroes: {:?}", zeroes);

// 在向量末尾添加和删除值。
let mut fruit = Vec::new();

fruit.push("Apple");
fruit.push("Banana");
fruit.push("Cherry");

println!("Pop off: {:?}", fruit.pop());

使用 if/esle 条件

Rust 中的if块也可充当表达式。

let formal = true;
let greeting = if formal {
  "Good day to you."
} else {
  "Hey!"
};
println!("{}", greeting)

使用哈希映射

HashMap<K, V>类型通过映射每个键K及其值V来存储数据。

use std::collections::HashMap;
let mut reviews: HashMap<String, String> = HashMap::new();

reviews.insert(String::from("Ancient Roman History"), String::from("Very accurate."));
reviews.insert(String::from("Programming in Rust"), String::from("Great examples."));

use 类似于其他编程语言所述的导入。

获取键值

let book: &str = "Programming in Rust";
println!("\nReview for \'{}\': {:?}", book, reviews.get(book));
// Review for 'Programming in Rust': Some("Great examples.")
// 由于 get 方法返回 Option<&Value> 类型,因此 Rust 使用"Some()"表示法包装方法调用的结果。

删除键值对

let obsolete: &str = "Ancient Roman History";
reviews.remove(obsolete);

println!("{:?}", reviews.get(obsolete));
// None

使用 for、while、和 loop 表达式

Rust 提供三种循环:

loop 可添加特定代码停止,也可输入 Ctrl+C 等键盘指令停止。

let mut counter = 1;

let stop_loop = loop {
  counter *= 2;
  if counter > 100 {
    break counter;
  }
};
// break when counter = 128
println!("counter = {}.", stop_loop)

断点返回相同类型的值。值类型都必须为整数、字符串或布尔等。未显示返回程序将表达式结果解释为空元组()

循环 while 语句

while counter < 5 {
  println!("We loop a while...");
  counter = counter + 1;
}

for 循环使用临时变量作为迭代器。该变量在循环表达式的开始位置隐式声明,并且每次迭代都会设置当前值。

let big_birds = ["ostrich", "peacock", "stork"];
for bird in big_birds.iter() {
  println!("{}", bird);
}

// 范围表达式 a..b
for number in 0..5 {
  println!("{}", number * 2);
}

在 Rust 中处理错误

伴随着 panic 的严重错误!

panic!宏将输出一条错误消息、清理资源、然后退出程序。

let v = vec![0, 1, 2, 3];
println!("{}", v[6]);// this will cause a panic!

使用 Option 类型处理缺失

Option<T>在 Rust 代码中的使用非常广泛。它可用于处理可能存在或可能为空的值。如果要在 Rust 中对可选字符串建模,则需要将其显示包装在Option类型中:Option<String>

enum Option<T> {
  None,   // The value doesn't exist
  Some(T) // The value exists
}

let fruits = vec!["banana", "apple", "cocount", "orange", "strawberry"];

let non_existent = fruits.get(99);
println!("{:?}", non_existent);
// None

模式匹配

可利用match运算符,通过提供模式来控制程序流。

let fruits = vec!["banana", "apple", "cocount", "orange", "strawberry"];
for &index in [0, 2, 99].iter() {
  match fruits.get(index) {
    Some(&"coconut") => println!("Coconuts are awesome!!!"),
    Some(fruit_name) => println!("It's a delicious {}!", fruit_name),
    None => println!("There is no fruit! :("),
  }
}

使用 match 表达式注意规则:

if let 表达式

let a_number = Option<u8> = Some(7);
if let Some(7) = a_number {
  println!("That's my lucky number!");
}

使用unwrapexpect

unwrap方法直接访问Option类型内部值。如果变体是None,则此方法将会 panic。
expect作用与unwrap相同,但它提供由第二个参数提供的自定义 panic 消息
考虑使用下列方法之一:

使用 Result 类型处理错误

#[derive(Debug)]
struct DivisionByZeroError;

fn safe_division(dividend: f64, divisor: f64) -> Result<f64, DivisionByZeroError> {
  if divisor == 0.0 {
    Err(DivisionByZeroError)
  } else {
    Ok(dividend / divisor)
  }
}

fn main() {
  println!("{:?}", safe_division(9.0, 3.0)); // Ok(3.0)
  println!("{:?}", safe_division(4.0, 0.0)); // Err(DivisionByZeroError)
  println!('{:?}', safe_division(0.0, 2.0)); // Ok(0.0)
}

Rust 如何管理内存

什么是所有权

作用域界定规则
Rust 中,作用域常常由大括号{}表示、常见作用域包括函数体、if else match分支。
所有权和删除
Rust 给范围的概念增加了一个转折。当对象超出范围时,便会将其”删除“。删除变量会释放与其关联的所有资源。
移动语义
有时,我们希望将某个项的所有权从一个绑定转移到另一个绑定。Rust 中,”转义所有权“被称为”移动“。

let mascot = String::from("ferris");
let ferris = mascot;

String的所有权从mascot转移到ferris之后,将无法再使用mascot变量。
函数中的所有权
将某个内容作为参数传递给函数,会将该内容移动到函数中。其他编程语言变量的值在传递给我们的函数之前可以隐式复制。但在 rust 中,此操作不会发生。
复制而不是转移
复制数字的成本低,因此复制这些值是有意义的。复制字符串、向量或其他复杂类型的成本可能高昂,因此它们没有实现Copy特征,而是被移动。
显式复制

fn process(s: String) {}

fn main() {
  let s = String::from("Hello, world!");
  process(s.clone());
  process(s);
}

了解借用

通过引用,可以”借用“一些值,而无需拥有它们。&
改变借用的值

fn main() {
  let mut greeting = String::from("hello");
  change(&mut greeting);
}

fn change(text: &mut String) {
  text.push_str(", world");
}

借用和可变引用
代码必须同时实现以下任一定义,但不能同时实现这两个定义:

使用生存期验证引用

声明周期使 Rust 能够在不产生垃圾收集性能开销的情况下确保内存安全。
在函数中注释生存期
多个生存期时,生存期批注帮助编译器了解它需要使用哪个生存期,以确保引用在运行时有效。

fn longest_word<'a>(x: &'a String, y: &'a String) -> &'a String {
  if x.len() > y.len() {
    x
  } else {
    y
  }
}

在类型中批注生存期

#[derive(Debug)]
struct Highlight<'document>(&'document str);

泛型数据类型

是根据其他部分未知类型定义的类型。使用泛型类型时,可以指定所需操作,而不必考虑定义类型持有的内部类型。

使用特征定义共享行为

特征是一组类型可实现的通用接口。

trait Area {
  fn area(&self) -> f64;
}
struct Circle {
  radius: f64,
}
struct Rectangle {
  width: f64,
  height: f64,
}

impl Area for Circle {
  fn area(&self) -> f64 {
    use std::f64::consts::PI;
    PI * self.radius.powf(2.0)
  }
}
impl Area for Rectangle {
  fn area(&self) -> f64 {
    self.width * self.height
  }
}

使用派生特征

Point类型没有实现以下特征:

#[derive(Debug, PartialEq)]
struct Point {
  x: i32,
  y: i32,
}

use std::fmt;
impl fmt::Display for Point {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    write!(f, "({}, {})", self.x, self.y)
  }
}

fn main() {
  let p1 = Point { x: 1, y: 2 };
  let p2 = Point { x: 4, y: -3 };

  if p1 == p2 {
    println!("equal!");
  } else {
    println!("not equal!");
  }

  println!("{}", p1);
  println!("{?:}", p1);
}

使用特征边界和泛型函数

我们可以通过定义函数的方式接受不同类型的函数,因为当类型实现特征时,可以将其抽象的视为该特征。

使用迭代器

Rust 中所有迭代器都会实现名为Iterator的特征,该特征在标准库中定义,并用于通过集合实现迭代器。
创建自己的迭代器涉及两个步骤:

  1. 创建一个结构来保留迭代器的状态。
  2. 实现该结构的迭代器。
#[derive(Debug)]
struct Counter {
  length: usize,
  count: usize,
}

impl Counter {
  fn new(length: usize) -> Counter {
    Counter {
      count: 0,
      length,
    }
  }
}

impl Iterator for Counter {
  type Item = usize;

  fn next(&mut self) -> Option<Self::Item> {
    self.count += 1;
    if self.count <= self.length {
      Some(self.count)
    } else {
      None
    }
  }
}

fn main() {
  for number in Counter::new(10) {
    println!("{}", number);
  }
}

探索模块、包和第三方箱

理解代码组织背后的概念

如果包中包含src/main.rssrc/lib.rs,则其中有两个箱:库文件和二进制文件。它们的名称与包相同。
模块是项的集合:

如果源文件中存在mod声明,则在运行编译器之前,系统会将模块文件的内容插入到mod声明在源文件中的所在位置。系统不会对模块进行单独编译,只会编译箱。

向项目中添加第三方箱