Rust 笔记

Posted risejl

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Rust 笔记相关的知识,希望对你有一定的参考价值。

结构体

初始化

Rust 的结构体类似于 C,使用关键字 struct 声明。

struct User 
  active: bool,
  sign_in_count: u32,
  username: String,
  email: String

结构体中的每个元素称为“域”(field),域是可修改的(mutable),使用 . 来访问域的值。

创建实例

为了使用结构体,需要根据结构体创建一个实例(instance),并给该结构体的域成员赋值,赋值的顺序可以不同于结构体定义的顺序。

使得域可修改,必须给实例添加 mut 关键字,Rust 不允许给某一个或几个域添加 mut 关键字。

struct User 
  active: bool,
  sign_in_count: u32,
  username: String,
  email: String


fn main() 
  let mut user1 = User 
    active: false,
    sign_in_count: 1,
    username: String::from("someusername"),
    email: String::from("someuseremail"),
  ;
  
  user1.email = "anotheremail";

可以使用 结构体更新语法 .. 来从其他实例来创建新实例:

struct User 
  active: bool,
  sign_in_count: u32,
  username: String,
  email: String


fn main() 
  let user1 = User 
    active: false,
    sign_in_count: 1,
    username: String::from("someusername"),
    email: String::from("someuseremail"),
  ;
  
  /*
  // regular 
  let user2 = User 
        active: user1.active,
        username: user1.username,
        email: String::from("another@example.com"),
        sign_in_count: user1.sign_in_count,
    ;
    
   */
  
  let user_2 = User 
    active: true,
    ..user1
  

上面的代码表示,除了域 active 之外,user_2 的其他域值和 user1 相等。

注:..user1 后没有 ,,而且必须放在最后一行。

元组结构体

元组结构体(tuple struct) 类似于元组。可以理解为给元组分配了有意义的名称,但是并没有确切的域成员,只有域成员的类型。

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

fn main() 
    let red = Color(0, 0, 0);
  	let origin = Point(0, 0, 0);

上面的两个实例虽然有着相同的域成员类型以及域成员值,但依然是不同的实例。

每个结构体实例的类型就是其定义的类型,即便它们有完全相同的域成员且域成员的类型一致。

类单元结构体

类单元结构体(unit-like struct)指的是不含任何数据的结构体。类似于不含成员的元组--单元(unit) ()

struct ULS;

fn main() 
    let subject = ULS;

函数 VS 方法

关联和区别

在一些编程语言中,函数(function)和方法(method)通常有着相同的含义。在 Rust 中,两者的关联和区别如下:

  • 关联
    • 都使用 fn 关键字声明
    • 都有参数和返回值
    • 都可以被调用
  • 区别
    • 方法的第一个参数永远是 self ,表示被调用的方法作用的实例(instance)
    • 方法通常被定义在一个结构体、枚举或者 trait 对象的上下文(context)下,而函数通常没有具体的上下文

Rust 使用方法的原因是提高代码的组织性。impl 紧紧关联着作用的结构体。

定义方法

方法使用 fn 关键字声明,通常写在 impl(implementation) 块(block)中。

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 are of rectangle ", rect1.area());

方法的第一个参数是 self,其实是 self: Self 的简洁表示。如果不希望方法带走 ownership,应该使用 &self,如果希望更改数据,使用 &mut self

类似函数,方法同样使用 . 运算符调用。与 C、C++ 等语言不同,Rust 不支持使用 -> 运算符来调用方法,而是通过被称为 自动引用和解引用 的方式来调用方法。大致原理为:当调用 object.method() 时,Rust 会自动添加 &&mut*,因此 object 匹配了方法的签名

以下两行代码作用相同:

p1.distance(&p2);
(&p1).distance(&p2);

getters

如果对结构体实现了同名的域成员和方法,那么 object.field 表示访问域成员,object.method() 表示调用方法。

通常,调用同名的方法表示希望获取其同名的域成员的值,这类方法被称为 getters。一些编程语言会自动实现 getters,但是 Rust 并非如此。

结合函数

定义在 impl 块下的函数都被称为 结合函数(Associated Function),因为它们作用于 impl 后的结构体。

也可以定义第一个参数不为 self 的结合函数,这类函数通过 :: 作用,例如:String::from()

impl Rectangle 
    fn square(size: u32) -> Self 
        Self 
            width: size,
            height: size,
        
    


let sq = Rectangle::square(3);

所以 :: 语法同时用于结合函数和模块(module)的命名空间。

多个参数的方法

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
  	

Rust 允许使用多个 impl 块声明方法,但是在本例中,两个方法放在一个 impl 中可读性更好。


枚举

枚举类型(enumerations / enums)定义穷举所有可能的值的数据类型。

定义枚举

例如下面的代码:

use std::cmp::Ordering;
use std:io;

fn main() 
		println!("This is a guessing game");
  	const SECRET_NUMBER: u32 = 12;
  	let mut guess: String = String::new();
  	println!("Enter your guess: ");
  	io::stdin.read_line(&mut guess).expect("Failed to read line");
  	let mut guess = match guess.parse().wrap() 
      Ok(num) => num,
      Err(_) => 
        println!("You should enter a number!");
      
	  
  	match guess.cmp::Ordering 
  		Less => println!("Too small!"),
      Greater => println!("Too big!"),
      Equal => println!("You are the winner!");
  

match 关键字用于开始匹配枚举。

这段代码中用到了两个枚举类型,分别是:

  1. guess 的类型判断会返回枚举类型 Result,它有 OkErr 两个枚举值,分别表示成功和错误两种情况,
  2. guessSECRET_NUMBER 两个值之间的比较会返回枚举类型 Ordering,它有 LessGreaterEqual 三种情况,分别表示小于,大于和等于。

模式匹配

在 Rust 中,使用 match 来对某个值和一系列模式进行匹配。模式可以是字面量、变量以及其他类型。

每个匹配(arm)都由模式代码组成,每个 arm 之间用 , 分隔。模式和代码之间用 => 相连。

如果代码为多行,需要放入括号 。代码由表达式组成,如果匹配成功,该表达式的值作为整个 match 的返回值。

例如:

enum Coin 
  	Penny,
  	Nickel,
  	Dime,
  	Quarter,


fn value_in_cents(coin: Coin) -> u8 
  	match coin 
      	/*
      	Coin::Penny => 
      			println!("That\'s an penny!");
      			1
      	,
      	*/
  			Coin::Penny => 1,
      	Coin::Nickel => 5,
      	Coin::Dime => 10,
      	Coin::Quarter => 25,
	  

matchif不同在于:if 的条件结果必须是 bool,而 match 可以是任意类型。

if let

if let 语法提供了一种更简洁的方式来处理某种模式匹配成功并忽略其他选项的情况。

let config_max = Some(3u8);
match config_max 
  	Some(max) => println!("The maximum is configured to be ", max),
  	_ => (),


// same as 
let config_max = Some(3u8);
if let Some(max) = config_max 
  	println!("The maximum is configured to be ", max);


模块系统

Rust 的模块系统(Module System)包括:

  1. 包(package):构建、测试、共享 crates。
  2. crates:可生成库(library)或者可执行程序的模块树。
  3. 模块(module):控制路径的组织方式、作用域以及私有性。
  4. 路径(path):命名一个实体的方式,例如:结构体、函数、模块。

是一系列 crates 的集合。包中有名为 Cargo.toml 的文件定义了如何构建这些 crates。

使用 cargo new 命令后,Rust 会在当前目录创建一个包。

包中至少要包含一个 crate。包可以包含多个二进制 crates,但是最多只能包含一个库 crate。

Cargo 是最常用的包,其默认把 src/main.rssrc/lib.rs 作为二进制 crate 和库 crate,并把两者作为 crate root。当使用 rustc 时,Rust 把这两个文件(如果存在)编译。

Crate

在 Rust 中,crate 指的是编译器所编译的源文件,是编译器一次编译时的最小单位。

crate 包含多个模块。

crate 分为二进制 crate(binary crate)和库(library crate)两种:

  • 二进制 crate 是由 Rustaceans 所编写的代码,每个二进制 crate 必须包含一个 main 函数。

  • 库 crate 不含 main 函数,不能被编译为可执行程序,而是作为一种共享方式在项目中。

两种 crate 分别在 src 路径下以 main.rslib.rs 两种文件名称存在。

crate root 指的是 Rust 编译器编译的源文件,以及 crate 的根模块。

模块

总览

假设有以下文件结构:

backyard
|--Cargo.lock
|--Cargo.toml
|--src
		|--garden
		|		|--vegetable.rs
		|--garden.rs
		|--main.rs
// src/main.rs

use crate::garden::vegetables::Asparagus;

pub mod garden;

fn main() 
    let plant = Asparagus ;
    println!("I\'m growing :?!", plant);

// garden.rs

pub mod vegetables;
// vegetables.rs

#[derive(Debug)]
pub struct Asparagus 
  • backyard 是 crate 目录
  • src/main.rs 是 crate root
  • pub mod garden 表示 garden 是一个模块,可见性为 pub。这行代码表示把 garden.rs 里的内容引入
  • pub mod vegetables 作用同上

所以,模块的工作原理:

  • 从 crate root 开始:当编译 crate 时,编译器首先找到 crate root 文件(通常是 main.rs 或者 lib.rs)来编译

  • 定义模块:在 crate root 文件中,可以用 mod 关键字声明新的模块,例如:mod garden,编译器会在以下目录寻找该模块的代码:

    • 行内
    • src/garden.rs
    • src/garden/mod.rs
  • 定义子模块:在 crate root 的文件里还可以定义子模块,例如:mod vegetables,编译器会在其父模块的目录下寻找子模块的代码:

    • 行内
    • src/garden/vegetables.rs
    • src/garden/vegetables/mod.rs
  • 模块中的路径:一旦声明模块后,可以通过路径引入模块。例如:在 vegetables 模块内声明了 Asparagus,引入路径为:crate::garden::vegetables::Asparagus

  • 私有 vs 共有:默认情况下,子模块的内容对父模块是私有的,使用 pub 关键字使其公有化

  • use 关键字:使用 use 来简化引用。

优势

模块的优势:

  • 提高代码的可读性和可重用性
  • 隐私性

路径

类似于文件系统,有绝对路径相对路径两种方式来表示层级关系:

  • 绝对路径:指的是从 crate root 开始的完整路径。对于外部 crate 来说,绝对路径以 crate 的名称为开始;对内部 crate 来说,绝对路径以字面量 crate 开始
  • 相对路径:从当前模块开始,通常包含 selfsuper 等关键字

绝对路径和相对路径使用 :: 表示层级间的分隔符。

绝对和相对路径各有优劣,可以根据个人偏好进行选择。在 Rust 中,一般使用绝对路径,原因是这样使得依赖相对独立。

mod front_of_house 
    mod hosting 
        fn add_to_waitlist() 
    


pub fn eat_at_restaurant() 
    // Absolute path
    crate::front_of_house::hosting::add_to_waitlist();

    // Relative path
    front_of_house::hosting::add_to_waitlist();

私有、公有

在 Rust 乃至整个计算机领域都有内部实现对外部不可见的原则。

私有的概念通常和作用域(scope)相关。例如:定义在函数 A 内部定义了子函数 B。对该子函数 B 来说,函数 A 对其是可见的。但对函数 A 的其他部分来说,子函数 B 是不可见的,

通常来讲,Rust 把对象(items)设置为私有(privacy),或者称为不可见的。如果调用了不可见的对象,编译器会弹出错误。

在 Rust 中,默认对父模块私有的对象(items)包括模块、函数、方法、结构体、枚举、常量

可以使用 pub 关键字使对象变为可见、公有的。

注:把某个外部对象标识为 pub 并不意味着其内部对象也被标识为 pub枚举类型除外,如果枚举类型使用了 pub,那么枚举的所有结果默认也为 pub),例如:

fn main() 
    pub fn outer_function() 
        fn inner_function() 
				    // --snip--
      	
  	
	  outer_function(); // OK
  	inner_function(); // Error, because the function sign has no **pub**


pub enum IPAddr 
	  V4(String), // also **pub**
  	V6(String), // also **pub**

最佳实践

一般来说,一个包同时包含二进制 crate src/main.rs以及库 crate src/lib.rs。两者默认都含有包名。

常用的范式是:在 src/lib.rs 中定义模块树,这样,在二进制 crate 中调用任何公有的对象(items)都可以以该包名为开始作为路径。

super

使用 super 关键字来引用父级路径,这类似于文件系统中的 ..

fn deliver_order() 

mod back_of_house 
    fn fix_incorrect_order() 
        cook_order();
        super::deliver_order();
    

    fn cook_order() 

use、as

use

使用 use 关键字来引入路径。

Rust 的惯例是:在调用某个函数时,其路径应该引入到父级。虽然引入到当前级效果相同,但是前者使得函数定义更加清晰。例如:

use crate::galaxy::solar_system::earth;
earth();

use crate::galaxy::solar_system;
solar_system.earth();              // same thing, but this one is better. 

再导出

再导出(re-exporting) 使得当前作用域引入的对象也可以用于其他作用域。因为默认情况下,使用 use 关键把某个名称引入当前作用域后,该名称对其他作用域是私有的。

使用 pub use 实现再导出:

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();

  • 如果没有再导出,外部代码想调用 add_to_waitlist() 函数必须使用路径:restaurant::front_of_house::hosting::add_to_waitlist()
  • 再导出后,使用 restaurant::hosting::add_to_waitlist() 即可

嵌套路径

为了避免使用多个 use 的多行引入导致代码可读性变差,可以把同一父对象下的子对象用花括号在同一行中。

use std::io, fmt;
// same as
use std::io;
use std::fmt;

如果同时引入了父对象和其子对象,使用 self 关键字表示该父对象。

use std::io::self, Result;
// same as
use std::io;
use std::io::Result;

Glob 运算符

如果要引入全部对象,使用全局 glob 运算符 *

use std::io::*;

as

假如引入的对象名称过长,可以使用 as 关键字来通过别名来引入。

use this_is_a_very_long_function_name as lfn;

lfn(); // much simpler

集合

Rust 的标准库中包含了一系列常用的数据结构被称为集合(collection)。最常用的是:

  • 向量 Vector
  • 字符串 String
  • 哈希表 HashMap

这些结构的特点是:存储在中,可变长,使用泛型实现。这意味着在编译时,编译器并不知道这些结构的大小。

初始化集合的通用方法是 ::new()

向量

向量中的元素在内存中紧挨着彼此存储。

向量只能存储同种类型的数据,但是可以借助枚举来存储不同类型的数据。

初始化

向量 Vec<T> 的初始化,可以用 ::new() 来初始化一个空向量,也可以使用 macro vec![] 显式把向量的成员列出来初始化向量:

fn main() 
    let v = vec![1, 2, 3, 4, 5];
    let ano_v: Vec<i32> = Vec::new();

注:在第二种声明中指明了存储元素的类型,否则 Rust 并不知道 Vector 要存储什么类型的数据。

读写

使用 push 方法给向量添加元素:

let mut v = Vec::new();
v.push(1);
v.push(2);

使用 .get() 或者括号索引 [] 的方式来访问向量元素:

let third: &i32 = &v[2]; // 3
let two: Option<&i32> = v.get(2); // Some(2)

如上面的代码所示,使用 .get() 方法得到的是 Option<T> 数据类型,而不是向量元素 <T> 的类型。

由于 .get() 方法得到的是 Option<T> 类型,因此可以使用 match 来对取得的值做判断。

let two = v.get(2);
match two 
    Some(two) => println!("The element is ", two),
    None => println!("No such element"),

越界

两种访问方式对于向量越界有着不同的处理方式:

let third = &v[100]; // index out of bound
let two = v.get(100); // None

使用 [] 索引访问可以通过编译,但在运行时会出现 index out of bound 索引越界的错误;使用 .get() 方法会得到 None

下面的例子说明了向量的工作方式:

let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
println!("", first); // Error

上面的代码不能通过编译,原因是:由于向量的元素在内存中是紧挨着彼此存储的。因此,给向量中添加新元素时,如果当前的内存空间位置不能容下新加入的元素,就需要分配一块新的内存空间并拷贝旧的元素到该空间。而引用访问向量元素也许会导致访问到已经被解除分配的空间。

遍历

使用 for in 来遍历向量元素:

for i in &v 
    println!("", i); 

使用 mut 引用来遍历并修改向量元素:

for i in &mut v 
    *i += 50;

字符串

两类字符串的比较

Rust 语言的核心中只有一种字符串:字符串切片 str,通常以 &str 形式出现。

String 类型由 Rust 标准库实现的。

两者都是 UTF-8 编码的。

初始化

字符串 String 的初始化,可以用 ::new() 来初始化一个空字符串,也可以用 ::from()显式初始化字符串,或者先声明字符串字面量,然后转化为 String 类型的字符串:

fn main() 
    let mut s = String::from("Hello");
  
    let mut ano_s = String::new();
  
	  let s_in_stack: &str = "Hello World";
		let s_in_heap: String = s_in_stack.to_string();

读写

使用 push_str() 把字符串拼接至另一字符串尾:

s.push_str(", World");
println!("", s) // Hello, World

使用 push() 拼接一个字符到字符串尾:

let mut s = String::from("lo");
s.push(\'l\');
println!("", s) // "lol"

使用 + 来拼接已有字符串:

let s1 = String::from("Hello, ");
let s2 = String::from("World");
let s = s1 + &s2;
println!("", s1); // Error
println!("", s2); // "World"
println!("", s); // "Hello, World"

注:拼接之后,s1 的 ownership 被转移给 s,所以 s1 不能再被使用。这是因为 add 函数的签名:

fn add(self, s: &str) -> String 

决定了 self 位置的变量的 ownership 被夺取。第二个变量需要使用 & 引用形式,而不是直接把两个字符串的值相加。

这里编译器使用 coerceString 类型转化为 &str,当调用 add 时,Rust 会使用 deref coercion&s2 转化为 &s2[..]

如果不希望 s1 的 ownership 发生变化,可以使用 format! macro 来拼接字符串:

let s1 = String::from("Hello, ");
let s2 = String::from("World");
let s = format!("s1s2");
println!("", s1); // "Hello, "
println!("", s2); // "World"
println!("", s); // "Hello, World"

Rust 不支持索引访问字符串中的字符。

let s1 = String::from("Code");
println!("", s1[0]); // Error

上面的代码将不能通过编译,原因和 String 类型的内部实现有关:

String 类型是对 Vec<u8> 的包装,所以:

let hello = String::from("Hola");
let ano_hello = String::from("Здравствуйте");

hellolen4,因为在 UTF-8 编码中每个字符占用 1 个字节,而 ano_hellolen24,而非 12,因为在 UTF-8 编码中,每个 Unicode scalar 值占用 2 个字节。因此,如果使用索引访问,将返回无意义的值。

可以使用 [..] 创建字符串切片:

let ano_hello = "Здравствуйте";
// let ano_hello = String::from("Здравствуйте"); // also Ok
let s = &ano_hello[0..4]; // Зд

sano_hello 的前 4 个字节,而非字符。

类型转换

使用 to_string 把其他类型转化为字符串:

let i: i32 = 2;
let i_s: String = i.to_string(); // "2";

遍历

使用 .chars() 获得字符串的序列,并用 for in 来遍历以输出字符串的字符:

let s = String::from("Hello");
for c in s.chars() 
    println!("", c);


// H
// e
// l
// l
// o

类似地,使用 .bytes() 或者字符对应的字节序列,并用 for in 来遍历以输出字符串的字符:

let s = String::from("Hello");
for b in s.bytes() 
    println!("", b);


// 72
// 101
// 108
// 108
// 111

哈希表

使用哈希表 HashMap<K, V> 前需要用 use 关键字引入:

use std::collections::HashMap;

哈希表的键类型为 String i32,键和值必须为相同类型。

初始化

可以用 ::new() 来初始化一个空哈希表:

fn main() 
    let hm = HashMap::new();

读写

使用 .insert() 添加键值对到哈希表(注意:mut 关键字):

let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 30);
scores.insert(String::from("Black"), 20);

使用 .get() 根据键获取值:

println!("the blue: :?", scores.get("Blue").unwrap()); // 10
println!("the blue: :?", scores.get("Yellow")); // Some(30)
println!("the blue: :?", scores.get("Red").copied().unwrap_or(0)); // None

和向量类似,获取的值是 Option<&V> 类型,可以使用 unwrap() 获取 <T> 类型。

遍历

使用元组遍历哈希表:

for (key, value) in &scores 
    println!(" ", key, value);


// One Possible Outcome:
	// Blue 10
	// Yellow 30
	// Black 20

注:遍历的结果是无序的。

更新

哈希表的更新有几种不同的方式:

如果给同一个键添加多个值,结果是只保留最后一个值:

use std::collections::HashMap;

fn main() 
    let mut map = HashMap::new();
  	map.insert(String::from("One"), 1);
		map.insert(String::from("One"), 2);
  	println!(":?", map); // "One": 2;

如果不存在键,使用 entry()or_insert() 添加键值对:

use std::collections::HashMap;

fn main() 
  	let mut map = HashMap::new();
  	map.insert(String::from("One"), 1);
  	map.entry(String::from("One")).or_insert(2);
  	map.entry(String::from("Two")).or_insert(2);
	  println!(":?", map); // "One": 1, "Two": 2;

entry() API 返回 Entry 枚举类型,该枚举类型返回指定的键是否存在,or_insert() 构建在 Entry 之上,如果键存在就不做修改,如果不存在就添加该键值对。

Rust学习笔记1.基础语法

文章目录

Rust的基本语法

0、猜字游戏

完成以下代码的运行,也就代表着基本了解Rust代码的运行逻辑,更代表自己的Rust开发环境已经正常。

use rand::Rng;
use std::cmp::Ordering;
use std::io;

fn main() 
    println!("Guess the number!");

    let secret_number = rand::thread_rng().gen_range(1..=100);

    println!("The secret number is:secret_number");

    println!("Please input your guess.");

    let mut guess = String::new();

    io::stdin()
        .read_line(&mut guess)
        .expect("Failed to read line");

    println!("You guessed: guess");

    match guess.cmp(&secret_number) 
        Ordering::Less => println!("Too small"),
        Ordering::Greater=>println!("Too big"),
        Ordering::Equal=>println!("You win"),
    

1、Cargo是什么

Cargo 是 Rust 的构建系统和包管理器。

2、输出字符串

函数println!和print!,ln会输出字符串最后的换行符。

注意,Rust的占位符为

3、基础语法

赋值

Rust是一门强类型语言,但是为了高并发安全所做的设计,存在可变与不可变变量。

使用let 进行赋值,加上关键字mut后成为可变变量

重影

重影与可变变量的赋值不是一个概念,重影是指用同一个名字重新代表另一个变量实体,其类型、可变属性和值都可以变化。但可变变量赋值仅能发生值的变化。

4、Rust的数据类型

  • 整数型
  • 浮点型
  • 布尔型
  • 字符型
  • 复合类型 注意()与[]的区别

()包含不同类型,[]包含相同类型

5、Rust的注释

一般是//与/*……*/

为说明文档注释时使用///

6、Rust的函数

fn <函数名> ( <参数> ) <函数体>

Rust不在乎何处定义函数,只需在某个地方定义它们即可。

函数体的语句和表达式

注意Rust可以使用返回值进行定义,如下图这个例子:

fn main() 
    let x = 5;

    let y = 
        let x = 3;
        x + 1
    ;

    println!("x 的值为 : ", x);
    println!("y 的值为 : ", y);

函数返回值

fn add(a: i32, b: i32) -> i32 
  return a + b;

7、条件语句

if-else语句,条件必须为bool类型

8、循环语句

  • while循环
  • for循环
  • loop循环

loop 循环可以通过 break 关键字类似于 return 一样使整个循环退出并给予外部一个返回值。这是一个十分巧妙的设计,因为 loop 这样的循环常被用来当作查找工具使用,如果找到了某个东西当然要将这个结果交出去。

9、所有权

所有权是Rust特有的资源管理机制,与别的语言的非常不同,需要注意。

Rust的所有权

变量范围,与相关

内存的分配


  char *s = strdup("runoob");
  free(s); *// 释放 s 资源*

实际上Rust没有什么确切的内存释放,而是依靠着编译器自带的内存释放,这里就使用到Rust的特定的功能,即所有权的问题。

变量与数据交互方式

移动

移动后会销毁签名那个变量,防止对值得二次销毁

克隆

多复制一份数据在堆里

垂悬引用

类似C++的空指针,在Rust里就是所引用的值已经被销毁。

10、切片

对数组或字符串数组进行的切片,即进行截取。

11、结构体

记住格式即可,与C/C++类似,但是Rust的结构体常常在面向对象里使用。

结构体类名 
    字段名 : 字段值,
    ...

12、枚举类

Rust的枚举类较为复杂。

enum Book 
    Papery(u32),
    Electronic(String),


let book = Book::Papery(1001);
let ebook = Book::Electronic(String::from("url://..."));

match语法

类似于switch语法,但是有相应改进。

match 枚举类实例 
    分类1 => 返回值表达式,
    分类2 => 返回值表达式,
    ...

Option枚举类

Option 是 Rust 标准库中的枚举类,这个类用于填补 Rust 不支持 null 引用的空白。

if let语法

只是区分两种情况的match语法糖

if let 匹配值 = 源变量 
    语句块

13、生命周期

生命周期是与所有权同样重要的资源管理机制

生命周期注释

生命周期注释是描述生命周期的发布方法

&i32        // 常规引用
&'a i32     // 含有生命周期注释的引用
&'a mut i32 // 可变型含有生命周期注释的引用

函数的生命周期参数

函数的生命周期声明是函数的入参和返回值的一种生命周期约定和限制

结构体定义的生命周期声明

当我们定义的struct的里面有对象引用的时候,我们需要在struct的模板参数中增加生命周期声明。

以下引用情况不需要使用生命周期

  • 函数的每个参数将会赋予各自的生命周期。
  • 如果输入参数只有一个生命周期参数,那个这个生命周期参数将会被赋予所有输入值。
  • 在struct的impl语句中,如果有多个输入参数,但是输入参数中有**&self或者&mut self**,那么self的生命周期将会被赋予所有的书参数。

泛型,特性,生命周期协同使用

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display

    println!("Announcement! ", ann);
    if x.len() > y.len() 
        x
     else 
        y
    

14、Rust组织管理

箱 crate

它是树状结构,存在于包中。

包 package

工程的实质就是一个包,包必须由Cargo.toml管理

模块Module

Rust的组织单位是模块。

类似于Java的类,JavaScript组织模块方式为function。

mod nation 
    mod government 
        fn govern() 
    
    mod congress 
        fn legislate() 
    
    mod court 
        fn judicial() 
    

在文件系统中,目录结构往往以斜杠在路径字符串中表示对象的位置,Rust 中的路径分隔符是 ::

路径分为绝对路径和相对路径。绝对路径从 crate 关键字开始描述。相对路径从 self 或 super 关键字或一个标识符开始描述。

访问权限

Rust的访问权限:公共与私有。

难以发现的模块

use关键字

使用use关键字可以将模块标识符引用当前作用域。

引用标准库

15、错误处理

Rust有一套独特的错误处理机制

不可恢复错误

panic!

可恢复错误

use std::fs::File;

fn main() 
    let f = File::open("hello.txt");
    match f 
        Ok(file) => 
            println!("File opened successfully.");
        ,
        Err(err) => 
            println!("Failed to open the file.");
        
    

如果想使一个可恢复错误按不可恢复错误处理,Result 类提供了两个办法:unwrap() 和 expect(message: &str) :

kind方法

kind函数可以获取Result的Err类型。

16、泛型与特性

泛型是每一个编程语言必不可少的一部分。

C++使用模板来实现。

在函数中定义泛型

fn max(array: &[i32]) -> i32 
    let mut max_index = 0;
    let mut i = 1;
    while i < array.len() 
        if array[i] > array[max_index] 
            max_index = i;
        
        i += 1;
    
    array[max_index]


fn main() 
    let a = [2, 4, 6, 3, 1];
    println!("max = ", max(&a));

结构体与枚举类的泛型

结构体与枚举类都可以定义方法,那么方法也应该实现泛型的机制,否则泛型的类将无法被有效的方法操作。

特性

特性的概念接近于Java中的接口,以及C++中的虚函数。

trait Descriptive 
    fn describe(&self) -> String;

Rust 同一个类可以实现多个特性,每个 impl 块只能实现一个。

17、面向对象

封装

总之,使用struct定义成员变量,使用类定义成员方法。

在Rust中可以用模块实现最外层的封装,每一个Rust都可以作为一个模块,模块内的天涯不属于可以通过pub对外明示。

“类”是面向对象编程常常用到的概念。“类”封装的是数据

在 Rust 中,我们可以使用结构体或枚举类来实现类的功能:

pub struct ClassName 
    pub field: Type,


pub impl ClassName 
    fn some_method(&self) 
        // 方法函数体
    


pub enum EnumName 
    A,
    B,


pub impl EnumName 
    fn some_method(&self) 

    

继承

Rust 没有提供跟继承有关的语法糖,也没有官方的继承手段(完全等同于 Java 中的类的继承),但灵活的语法依然可以实现相关的功能。

18、文件与IO

接受命令行参数

环境参数需要std::env取出

fn main() 
    let args = std::env::args();
    println!(":?", args);

文件读取

读取文本文件:

use std::fs;

fn main() 
    let text = fs::read_to_string("D:\\\\text.txt").unwrap();
    println!("", text);

读取二进制文件:

use std::fs;

fn main() 
    let content = fs::read("D:\\\\text.txt").unwrap();
    println!(":?", content);

文件流读取方式:

use std::io::prelude::*;
use std::fs;

fn main() 
    let mut buffer = [0u8; 5];
    let mut file = fs::File::open("D:\\\\text.txt").unwrap();
    file.read(&mut buffer).unwrap();
    println!(":?", buffer);
    file.read(&mut buffer).unwrap();
    println!(":?", buffer);

std::fs 模块中的 File 类是描述文件的类,可以用于打开文件,再打开文件之后,我们可以使用 File 的 read 方法按流读取文件的下面一些字节到缓冲区(缓冲区是一个 u8 数组),读取的字节数等于缓冲区的长度。

文件写入

一次性写入:

use std::fs;

fn main() 
    fs::write("D:\\\\text.txt", "FROM RUST PROGRAM")
        .unwrap();

流写入:

use std::io::prelude::*;
use std::fs::File;

fn main() 
    let mut file = File::create("D:\\\\text.txt").unwrap();
    file.write(b"FROM RUST PROGRAM").unwrap();

19、集合与字符串

集合是数据结构中最普遍的数据存放形式。

向量

向量(Vector)是一个存放多值的单数据结构,该结构将相同类型的值线性的存放在内存中。

向量是线性表,在 Rust 中的表示是 Vec。

向量的使用方式类似于列表(List),我们可以通过这种方式创建指定类型的向量:

let vector: Vec<i32> = Vec::new(); // 创建类型为 i32 的空向量
let vector = vec![1, 2, 4, 8];     // 通过数组创建向量

常用方法:

  • append将一个向量拼接到另一个向量尾部

  • get方法取出向量的值(同样可以使用数组表示法)

字符串

常用函数:

let mut s = String::from("run");
s.push_str("oob"); // 追加字符串切片
s.push('!');       // 追加字符

let s1 = String::from("Hello, ");
let s2 = String::from("world!");
let s3 = s1 + &s2;

let s1 = String::from("tic");
let s2 = String::from("tac");
let s3 = String::from("toe");
let s = s1 + "-" + &s2 + "-" + &s3;

//求字符长度
let s = "hello";
let len = s.len();
// 因为中文每个字符长3字节。统计字符数量应该先统计字符集合
let s = "hello你好";
let len = s.chars().count();

//遍历字符串
fn main() 
    let s = String::from("hello中文");
    for c in s.chars() 
        println!("", c);
    


//从字符串中取出单个字符
fn main() 
    let s = String::from("EN中文");
    let a = s.chars().nth(2);
    println!(":?", a);

//注意,nth不能用在遍历中

//注意截取字符串时必须特别注意UTF-8字符,以下程序会报错
fn main() 
    let s = String::from("EN中文");
    let sub = &s[0..3];
    println!("", sub);

映射

映射表(Map)在其他语言中广泛存在。其中应用最普遍的就是键值散列映射表(Hash Map)。

use std::collections::HashMap;

fn main() 
    let mut map = HashMap::new();

    map.insert("color", "red");
    map.insert("size", "10 m^2");

    println!("", map.get("color").unwrap());

以上是关于Rust 笔记的主要内容,如果未能解决你的问题,请参考以下文章

Rust学习笔记 | 01 - Rust快速入门(为什么是Rust开发环境搭建Cargo的使用HelloWorldRust依赖包crates)

「Rust笔记」Rust之自定义宏写法

「Rust进阶笔记」Rust之derive特性总结

Rust 笔记

rust学习笔记-变量和类型

rust学习笔记-变量和类型