Rust语言基本语法

可以参考这里
或这里

rust中文在线文档

1 安装

在ubuntu下安装,执行:

echo "export RUSTUP_DIST_SERVER=https://mirrors.ustc.edu.cn/rust-static" >> ~/.bashrc
echo "export RUSTUP_UPDATE_ROOT=https://mirrors.ustc.edu.cn/rust-static/rustup" >> ~/.bashrc
source .bashrc
 
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
#curl https://sh.rustup.rs -sSf | sh
# rustup update # 更新rust

安装完毕后刷新环境变量:

source ~/.cargo/env

上面安装好rust的相关软件,然后在.bashrc里面添加环境变量,重启终端生效,或者执行source .bashrc,添加内容如下:

export PATH=$PATH:$HOME/.cargo/bin:$PATH	#$

测试:

cargo -V # 查看cargo版本
rustc -V # 查看rust版本

安装 rust 的 vim 插件:

git clone https://github.com/rust-lang/rust.vim.git
mv rust.vim .vim/bundle/

VScode上编写、调试rust

1 安装号VScode后,再从商店里面安装两个插件:

  • Rust(rls)
  • Native Debug

重启生效;

2 用vscode打开创建好的rust项目,点击左侧调试按钮,在这里点击创建launch.json文件,选择环境GDB
3 当前目录下会生成.vscode文件夹,在该目录下手动创建两个文件:tasks.json、launch.json,添加如下内容:
tasks.json

{ 
    "version":"2.0.0", 
    "tasks":[ 
        { 
            "label":"build", 
            "type":"shell", 
            "command":"cargo", 
            "args":["build"] 
        } 
    ],
    "presentation": {
        "echo": true,
        "reveal": "always",
        "focus": false,
        "panel": "new",
        "showReuseMessage": true,
        "clear": false
    }
}

launch.json

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug",
            "type": "gdb",
            "preLaunchTask": "build",
            "request": "launch",
            "target": "${workspaceFolder}/target/debug/${workspaceFolderBasename}",
            "cwd": "${workspaceFolder}"
        }
    ]
}

添加完毕后,即可点击按钮调试;

2 编写、运行rust程序

  • 创建 hello.rs 文件
  • 编写内容:
    fn main() {
    	println!("hello, world!");
    }
    
  • 编译
    rustc hello.rs
    
  • 运行
    ./hello
    
  • 查看文档
    cargo doc --open
    

3 cargo 命令

  • 新建工程
    cargo new hello_cargo
    
    生成的目录中包含:Cargo.toml文件、src文件夹、.git文件夹
  • 编译
    cargo build
    cargo build --release
    
  • 运行
    cargo run
    
    或者运行 ./target/debug/helllo_cargo,这是通过 cargo build 命令产生的可执行文件;
  • 检查
    ``bash
    cargo check
    用于检查是否能编译,一般检查语法时用这个命令,速度比 cargo build 快;
    
    

4 常用语句

  • 获取键盘输入
    fn main() {
     	let mut a = String::new();
     	std::io::stdin().read_line(&mut a).expect("Failed to read line.");
     	println!("{}",a);
    }
    

5 变量与常量

5.1 变量

  • 不可变变量(默认)
  let x = 1;

不能第二次赋值

  • 可变变量
    let mut x = 1;
    
    可以第二次赋值

5.2 常量

const MAX_POINTS: u32 = 100_000;

5.3 隐藏

fn main() {
	let x = 5;
	let x = x + 1;
	let x = x * 2;
	println!("{}", x);
}

重复创建x变量会隐藏前面的变量,在每次let时,x的类型可以改变,本质是重新创建了一个变量;

6 数据类型

有4种标量类型:整形、浮点型、布尔型、字符型

  • 整形
    不同长度类型:

    长度 有符号 无符号
    8-bit i8 u8
    16-bit i16 u16
    32-bit i32 u32
    64-bit i64 u64
    128-bit i128 u128
    atch isize usize

    不同进制的表示方式:

    进制 例子
    98_222 或者 98222
    十六 0xff
    0o77
    0b1111_0000 或 0b11110000
    byte(u8) b’A’
  • 浮点型

    let x = 2.0; // f64,默认64位
    let y: f32 = 3.0; // f32
    
  • 布尔型

    let t  = true;
    let f: bool = false;
    
  • 字符型

    let c = 'a';
    

6.1 数学运算符

Rust 中不同数据类型的常量定义参考这里
Rust 中自带有数学运算,但数学运算需要特定的数据类型,所以在运算前要确保数据类型已经转换为对应的格式:

println!("5+4= {}", 5 + 4);
println!("5-4= {}", 5 - 4);
println!("5*4= {}", 5 * 4);
println!("15/4= {}", 15 / 4);
println!("18%4= {}", 18 % 4);

println!("2^6 = {}", 2i32.pow(6));
println!("sqrt 9 = {}", 9f64.sqrt());
println!("27 cbrt 9 = {}", 27f64.cbrt());
println!("Round 1.45 = {}", 1.45f64.round());
println!("Floor 1.45 = {}", 1.45f64.floor()); // 返回小于它的最大整数
println!("Ceiling 1.45 = {}", 1.45f64.ceil());
println!("e ^2 = {}", 2f64.exp());
println!("log(2)= {}", 2f64.ln());
println!("log10(2) = {}", 2f64.log10());
println!("90 to Radians = {}", 90f64.to_radians());
println!("PI to Degrees = {}", 3.1415f64.to_degrees());
println!("sin(3.1415) = {}", 3.1415f64.sin());

println!("Max(4,5) = {}", 4f64.max(5f64));
println!("Min(4,5) = {}", 4f64.min(5f64));

7 元组和数组

  • 元组
    元组的元素可以是不同的类型,元组长度固定,一旦声明,其长度不会改变;从元组中取出元素有下面两种方式:

    fn main() {
    let tup = (1, 1.1, 200);
    // let tup: (i32, f64, u64) = (1, 1.1, 200);
    let (x, y, z) = tup;
    let a = tup.1;
    println!("{}",y);
    println!("{}",a);
    }
    
  • 数组 array
    其元素的类型必须一样:

    let a = [1, 2, 3, 4];
    let b = ["abc", "abcd", "abcde"];
    let c: [i32; 5] = [1, 2, 3, 4, 5];
    let d = [3; 5];
    println!("{}", d[0]);
    

    访问数组时,如果索引超出了数组的范围,编译可以正常通过,但运行时会报这个访问超出范围的错;

  • slice
    slice 是对数组 array 的切片,所以通过 slice 可以获取 array 的部分或全部的访问权限:

    let arr = [1, 2, 3, 4, 5, 6];
    let slice_complete = &arr[..]; // 获取全部元素
    let slice_middle = &arr[1..4]; // 获取中间元素,最后取得的Slice为 [2, 3, 4] 。切片遵循左闭右开原则。
    let slice_right = &arr[1..]; // 最后获得的元素为[2, 3, 4, 5, 6],长度为5。
    let slice_left = &arr[..3]; // 最后获得的元素为[1, 2, 3],长度为3。
    
  • 函数类型
    可以创建变量指向函数:

    fn foo(x: i32) -> i32 { x+1 }
    let x: fn(i32) -> i32 = foo;
    assert_eq!(11, x(10));
    

8 函数

注意函数最后一句没有 ‘;’ 号:

fn main() {
    let x = plus_one(5);
    println!("{}", x);
}
// 这里函数的传入参数和传出参数都是所有权,如何使用引用权、借用权在后文描述
fn plus_one(x: i32) -> i32 {
    x + 1
}

9 控制流

9.1 if

注意 if 的条件不加括号,和c语言不同,其他地方和c语言一样:

fn main() {
    let x = true;
    let y = 1;
    if x && (y == 1) {
        println!("1")
    } else if y == 2 {
        println!("2")
    } else {
        println!("3")
    }
}

if 表达式也可用在 let 语句的右侧,但需要在 if 表达式的最后面加上 ‘;’,并且每个条件返回的数据类型要一致,如:

fn main() {
    let condition = true;
    let n = if condition {
        5
    } else {
        6
    };
    println!("{}", n);
}

9.2 loop

如果不终止,该循环会一直执行下去:

fn main() {
	loop {
		println!("again!");
	}
}

可以用 break 语句来终止循环,break 后面可以跟上要返回的值:

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };
    println!("{}", result);
}

9.3 while

fn main() {
    let mut n = 3;
    while n != 0 {
        println!("{}", n);
        n = n - 1; // rust不能使用 n++ 或 n--
    }
}

9.4 for

类似于 python:

fn main() {
    let a = [10, 20, 30, 40];

    for i in a.iter() {
        println!("{}", i); // 依次打印 10, 20, 30 40
    }
}
fn main() {

    for i in (1..5).rev() {
        println!("{}", i); // 依次打印 4, 3, 2, 1
    }
}

9.5 break 和 continue

这两个关键字在 rust 中也可以使用,同其他语言一样;

9.6 label

由于经常会使用到嵌套循环,此时如果让 break 和 continue 去直接操作对应层的循环,就可以使用到 label 功能:

'outer: for x in 0..10 {
    'inner: for y in 0..10 {
        if x % 2 == 0 { continue 'outer; }
        if y % 2 == 0 { continue 'inner; }
        println!("x: {}, y: {}", x, y);
    }
}

  • 注释
    // abcd
    /* abcd */
    
  • 新建变量
    let x = 20;
    
  • 打印
    println!("x = {}", x); // {}与x对应
    

10 所有权

先解释以下堆和栈,一般而言,数据占用已知且固定大小,就可以把这类数据存放在栈中,如果数据占用的大小未知,而且是在程序中产生,那么这类数据会放在堆中;栈在内存中是连续的内存空间,用进栈、出栈的方式管理,堆的产生需要向系统申请一段足够大的内存空间,位置不连续;访问堆的时间比栈长;

RUST 的所有权主要用于管理内训,所有权有以下几个规则:

  • Rust中的每一个值都有一个被称为所有者的变量;
  • 值有且只有一个所有者;
  • 当所有者(变量)离开作用域,这个值将被丢弃;

10.1 String 类型

这个类型不仅仅存放在栈上,它的值会存放在堆上,所以能够存储在编译时未知大小的文本;相关的一些使用如下:

let mut  s = String::from("hello");
s.push_str(", world!");
println!("{}", s);

10.2 变量与数据交互的方式

10.2.1 方式一:移动

分析几个例子:

  • 移动普通数字变量

    	let x = 5;
    	let y = x;
    

    在上面的代码中,两个变量的值都为5,因为它们内存大小固定,将存放在栈中,栈里的变量移动时相当于复制;

  • 移动String类型

    	let s1 = String::from("hello");
    	let s2 = s1;
    

    String类型的值存放在堆中,所以应该会有深拷贝和浅拷贝的问题,不过在Rust中,这里s2 = s1更像是浅拷贝,但与浅拷贝不同的是这里s1将会变得无效,同时,s1把所有权交给了s2,比如下面这段代码不能运行:

    	let s1 = String::from("hello");
    	let s2 = s1;
    	println!("{}", s1);
    

    Rust语言基本语法_第1张图片
    换句话说,s1把所有权交给了s2,自己就失效了,因为一个值只有一个变量具有所有权;下面这段代码能运行:

    	let s1 = String::from("hello");
    	let s2 = s1;
    	println!("{}", s2);
    

10.2.1 方式二:克隆

克隆也是深拷贝,不仅仅是栈上的数据,堆上的数据也被复制了,如下:

	let s1 = String::from("hello");
	let s2 = s1.clone();
	println!("s1 = {}, s2 = {}", s1, s2);

10.3 所有权与函数

先看两段代码:

fn main() {
	let s: i32 = 1;
	fun1(s);// s复制了一份传入函数
	// 到这里s还能使用
}
fn fun1(arg: i32) {
	printl!("{}", arg);
}	
fn main() {
	let s = String::from("hello");
	fun2(s);// s传递到函数中,自己销毁了(其实是s对应的栈数据销毁了,复制了一个新的栈数据替代它,并传入函数)
	// 到这里s不能使用了
}
fn fun2(arg: String) {
	printl!("{}", arg);// arg会销毁
}	

对比上面两组代码可以发现,传递变量到函数中,默认使用的是“=(移动)”操作,如果这个变量值存在于栈中(如i32),就相当于复制,如果还存在于堆中(如String),原数据将会被销毁;
对于这种所有权的传递方式,怎么能让传入参数不丢失呢?解决方案是在函数的末尾又传出来,如下:

fn main() {
	let s1 = String::from("hello");
	let s2 = fun1(s1);
}
fn fun1(s: String) -> String {
	printl!("{}", s);
	s
}

也可以返回多个参数:

fn main() {
	let s1 = String::from("hello");
	let (s2, len) = fun1(s1);
}
fn fun1(s: String) -> (String, usize) {
	printl!("{}", s);
	let length = s.len();
	(s, length)
}

10.4 引用和可变引用

上面的函数采用所用权传递,对传入参数影响较大,对此,可以通过引用来解决这个问题,如下:

fn main()	{
	let	s1	=	String::from("hello");
	let	len	=	calculate_length(&s1);
	println!("The	length	of	'{}'	is	{}.",	s1,	len);
}
fn calculate_length(s:	&String) ->	usize {
	s.len()
}

引用就是在变量类型的前面加上 & 符号,默认情况下函数就只有使用它的权利,没有更改、销毁的权利,如果要有修改的权利,就是可变引用,如下:

fn	main()	{
	let	mut	s	=	String::from("hello");
	change(&mut	s);
}
fn	change(some_string:	&mut	String)	{
	some_string.push_str(",	world");
}

10.5 Slice 类型

11 结构体

11.1 定义、使用结构体

  • 1 定义结构体

    struct User {
        username: String,
        email: String,
        sign_in_count: u64,
        active: bool,
    }
    
    
  • 2 实例化结构体

    let user1 = User {
        email: String::from("[email protected]"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
  • 3 改变成员的值
    如果要改变成员的值,那么在实例化结构体的时候就要定义成 mut 类型的,然后就可以在外部更改:

    let mut user1 = User {
        email: String::from("[email protected]"),
        username: String::from("someusername123"),
        active: true,
        sign_in_count: 1,
    };
    
    user1.email = String::from("xxxxx.com");
    
  • 4 结构体作为函数参数

    // 调用函数
    let user1 = build_user(String::from("xxx.com"), String::from("xxx"));
    
    // 定义函数
    fn build_user(email: String, username: String) -> User {
        User {
            email: email,
            username: username,
            // 为了简化上面的重复名称,可以替换成下面这样:
            // email,
            // username,
            active: true,
            sign_in_count: 1,
        }
    }
    
    
  • 5 如果新建的实例内容和已经存在的实例有重复的,可以用下面的方法简化代码:

    // 不简化
    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("anotherusername567"),
        active: user1.active,
        sign_in_count: user1.sign_in_count,
    };
    
    // 简化
    let user2 = User {
        email: String::from("[email protected]"),
        username: String::from("anotherusername567"),
        ..user1 // ..表示余下的所有其他成员都保持一致
    };
    
  • 6 也可定义与元祖类似的结构体:

    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);
    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
    

11.2 打印结构体

显然直接用 println!("{}", user1); 是不行的,打印之前需要在开头加上 #[derive(Debug)],并且使用 println!("{:?}", user1); 来打印,打印信息如下:

User { name: "123", email: "345", age: 1, active: true }

也可以改变打印的格式,比如是否换行,这只需要改变打印的参数,将 {:?} 改为 {:#?}

11.3 给结构体添加方法

观察 impl 中各个函数的参数:

#[derive(Debug)]

struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle { // impl + 结构体名
    fn area(&self) -> u32 { // 也可以换成 (&mut self)
        self.width * self.height
    }

    fn can_hold(&self, other: &Rectangle) -> bool { // 可以定义多个方法,self是自身
        self.width > other.width && self.height > other.height
    }

}

// 可以多处使用 impl 块
impl Rectangle {
    // 参数中没有self,称关联函数,外部调用是用 :: 符号
    fn square(size: u32) -> Rectangle { 
        Rectangle { width: size, height: size }
    }
}


fn main() {
    let rect1 = Rectangle { width: 30, height: 50 };
    println!("The area of the rectangle is {} square pixels.",rect1.area());
    // 调用关联函数
    println!("{:?}", rect1::square(3));
}

12 枚举

12.1 定义枚举

  • 1 定义

    enum IpAddrKind {
        V4,
        V6,
    }
    
    let four = IpAddrKind::V4;
    let six = IpAddrKind::V6;
    
  • 2 作为函数参数
    ``rust
    enum IpAddrKind {
    V4,
    V6,
    }
    fn route(ip_type: IpAddrKind) { }
    route(IpAddrKind::V4);
    route(IpAddrKind::V6);

    
    
  • 3 每个成员可以有自己的数据类型
    如:

    enum IpAddr {
        V4(u8, u8, u8, u8),
        V6(String),
    }
    let home = IpAddr::V4(127, 0, 0, 1);
    let loopback = IpAddr::V6(String::from("::1"));
    

    数据类型也可以是结构体:

    struct Ipv4Addr {
        // --snip--
    }
    struct Ipv6Addr {
        // --snip--
    }
    enum IpAddr {
        V4(Ipv4Addr),
        V6(Ipv6Addr),
    }
    
  • 4 也可以用 impl 定义方法

    enum Message {
        Quit,
        Move { x: i32, y: i32 },
        Write(String),
        ChangeColor(i32, i32, i32),
    }
    
    impl Message {
        fn call(&self) {
            // 在这里定义方法体
        }
    }
    let m = Message::Write(String::from("hello"));
    m.call();
    
  • 5 用 option 解决空值问题

    enum Option {
        Some(T),
        None,
    }
    

    可以这样赋值:

    let some_number = Some(5);
    let some_string = Some("a string");
    let absent_number: Option = None;
    

    如果变量的值有可能为空,就需要用 Option 来修饰,然后通过判断,只对其不为空的值做处理,如:

    let mut a: Option;
    //a = None;
    a = Some(1);
    
    // 解析 Option 方法一:
    if let Some(i) = a { // 这里的 i 就相当于 a 里的值,因为 a 是个Option,值不能直接访问
    	println!("{}", i);
    }
    
    // 解析 Option 方法二:
    let v = a.unwrap();
    println!("{}", v);
    
    // 解析 Option 方法三:
    match a.as_mut() {
        Some(v) => println!("{}", *v),
        None => {},
    }
    
    // 判断 Option 是否有值
    if a.is_some() {
    	println!("有值" );
    }
    
  • 6 Result 用于函数的返回,处理错误情况

    // 定义
    enum Result {
    Ok(T),
    Err(E),
    }
    
    // 使用
    let f = File::open("hello.txt"); // 返回值就是 Result 类型
    match f {
        Ok(file) => {
            println!("File opened successfully.");
        },
        Err(err) => {
            println!("Failed to open the file.");
        }
    }
    

12.2 match

  • 1 match 用于过滤枚举的值,逻辑像 swich,哪一个条件匹配了就执行该条件后面的语句:

    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter,
    }
    
    // 这里可以用 impl 方法把函数绑在 Coin 中
    fn value_in_cents(coin: Coin) -> u8 {
        match coin {
            Coin::Penny => {
                println!("Lucky penny!");
                1
            },
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter => 25,
        }
    }
    
    fn main() {
        let coin = Coin::Nickel;
        println!("{}", value_in_cents(coin));
    } 
    
  • 2 枚举还可以嵌套,match 同样可以使用:

    #[derive(Debug)]
    enum UsState {
        Alabama,
        Alaska,
    }
    
    enum Coin {
        Penny,
        Nickel,
        Dime,
        Quarter(UsState),
    }
    
    fn value_in_cents(coin: Coin) -> u8 {
        match coin {
            Coin::Penny => 1,
            Coin::Nickel => 5,
            Coin::Dime => 10,
            Coin::Quarter(state) => {
                println!("State quarter from {:?}!", state);
                25
            },
        }
    }
    
  • 3 关于 Option,和枚举一样使用:

    fn plus_one(x: Option) -> Option {
        match x {
            None => None,
            Some(i) => Some(i + 1),
        }
    }
    let five = Some(5);
    let six = plus_one(five);
    let none = plus_one(None);
    
  • 4 match 需要把所有的情况罗列出来,如果不想把所有的罗列出来,可以使用通配符_,如:

    let some_u8_value = 0u8;
    match some_u8_value {
        1 => println!("one"),
        3 => println!("three"),
        5 => println!("five"),
        7 => println!("seven"),
        _ => (), // 其他情况会自动罗列完
                // 如果不罗列完所有情况,编译会查得出来
    }
    

    如果很多种情况,而只需要操作其中一种情况,就可以用上面的方式解决,代码像这样:

    let some_u8_value = Some(0u8);
    match some_u8_value {
        Some(3) => println!("three"),
        _ => (),
    }
    

    对此,有一个更简单的方法,就是使用 if let

    # let some_u8_value = Some(0u8);
    if let Some(3) = some_u8_value {
        println!("three");
    }
    

12.3 if let简单控制流

如果只想匹配枚举中的单个选项,可以使用 if let 来简化 match,如:

let some_u8_value = Some(0u8);
//match some_u8_value {
//Some(3) => println!("three"),
//_ => (),
//}
if let Some(3) = some_u8_value {
println!("three");
}

13 组织管理

一般的工程项目都会涉及到很多个文件,并且需要在不同的文件中是实现不同的功能,如何将这些文件组织在一起是首先要解决的问题;Rust对此的处理能让项目文件的结构很清晰,各种模块、功能的分类结构和文件夹结构非常相似;

13.1 创建模块、调用模块

在新建的Ruat项目中,只存在 src/main.rs 源程序,我们另外创建一个 my_lib.rs 文件,src 文件结构如下:

── src
   ├── my_lib.rs
   └── main.rs

在 my_lib.rs 文件中添加 fun1 函数和 module1 模块,代码如下:
my_lib.rs # 文件名本身就是模块名,如 my_lib,文件里的模块等归属在它下面

pub fn fun1() {} // pub 表示公开,外部能访问,不使用的话默认私有

pub mod module1 {
	pub mod module1_1 { // 模块可以内嵌
		pub fn fun2() {}
	}
	pub fn fun3() {}
}

在 main.rs 文件中调用 fun1函数 和 module1 中的函数:
main.rs

mod my_lib; // 引用 my_lib.ts 文件这个模块
pub use crate::my_lib::module1;

fn main() {
	my_lib::fun1(); // 调用 my_lib.rs 中的 fun1 函数
	module1::module1_1::fun2(); // 调用 my_lib.rs 中的模块
	module1::fun3();
}

13.2 模块的结构和文件夹的结构关系

再据一个例子来说明其组织管理和文件夹结构的关系,在上面的代码基础上,在 my_lib 中再添加模块 module2:
my_lib.rs

pub fn fun1() {} // pub 表示公开,外部能访问,不使用的话默认私有

pub mod module1 {
	pub mod module1_1 { // 模块可以内嵌
		pub fn fun2() {}
	}
	pub fn fun3() {}

pub mod module2;// 需要创建一个 rs 文件来添加里面的内容

然后在 my_lib.rs 的统计目录下创建 my_lib 文件夹,并在 my_lib 文件夹下创建 module2.rs 文件,并添加代码:
my_lib.rs

pub fn fun4() {}

调用方式和前面一样:
main.rs

mod my_lib; 
pub use crate::my_lib::module2;

fn main() {
	module2::fun4();

该实验的文件结构:

── src
   ├── my_lib
   │   └── module2.rs
   ├── my_lib.rs
   └── main.rs

13.3 crate、super、use、as关键词

crate 表示根目录
super 表示父目录,也就是父模块
use 使用模块的作用域
as 给作用于提供新名称

use std::io::Result as IoResult;

13.4 创建模块

用下面命令可以单独创建库工程(或像前文一样直接添加 .rs 文件,并在里面实现模块):

cargo new --lib <库名称>

创建完毕后,里面自带测试代码:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

执行下面命令即可测试:

cargo test
//cargo test -- --nocapture

注:库的文档通常写在文档注释中,注释///在任何项之前开始,或//!记录父项;

13.5 使用外部包

13.6 推荐的目录结构

.
├── Cargo.lock
├── Cargo.toml
├── src/
│   ├── lib.rs
│   ├── main.rs
│   └── bin/
│       ├── named-executable.rs
│       ├── another-executable.rs
│       └── multi-file-executable/
│           ├── main.rs
│           └── some_module.rs
├── benches/
│   ├── large-input.rs
│   └── multi-file-bench/
│       ├── main.rs
│       └── bench_module.rs
├── examples/
│   ├── simple.rs
│   └── multi-file-example/
│       ├── main.rs
│       └── ex_module.rs
└── tests/
    ├── some-integration-tests.rs
    └── multi-file-test/
        ├── main.rs
        └── test_module.rs

Cargo.toml 与 Cargo.lock 存储在项目的根目录中。
源代码进入 src 目录。
默认库文件是 src/lib.rs
默认的可执行文件是 src/main.rs
其他可执行文件可以放入 src/bin/*.rs
集成测试进入 tests 目录(单元测试进入他们正在测试的每个文件中)。
示例可执行文件放在 examples 目录中。
基准测试进入 benches 目录。

14 数据集合

Rust 中广泛使用的数据集有3类:Vector、String、HashMap;这些数据集的内容一般存放在堆上,由程序运行时动态分配;

14.1 Vector

  • 新建 vector:

    let v: Vec = Vec::new(); // 创建空的vector,类型i32
    let v: Vec = Vec::with_capacity(10); // 和上面new一样,但会预先分配10个成员空间,效率稍微比new高点
    let v = vec![1, 2, 3]; // 新建一个拥有值1、2、3的 Vec
    
  • 更新 vector :

    let mut v = Vec::new();
    v.push(5); // 增加元素
    v.push(6);
    v.push(7);
    v.push(8);
    
  • 读取 vector 的元素
    读取有两种方式:索引法、get方法,如果超出了范围,索引法会在程序运行时报错,而 get 方法会返回 None,在实际运用中考虑这两者的区别;

    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."),
    }
    
  • 遍历 vector

    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;
    }
    
  • 在 vector 中存放不同类型的值可以搭配枚举来实现:

    enum SpreadsheetCell {
    		Int(i32),
    		Float(f64),
    		Text(String),
    }
    let row = vec![
    		SpreadsheetCell::Int(3),
    		SpreadsheetCell::Text(String::from("blue")),
    		SpreadsheetCell::Float(10.12),
    ];
    

14.2 String

  • 新建字符串string

    // 创建空的string
    let mut s = String::new();
    
    // 从 str -> string
    let data = "initial contents";
    let s = data.to_string();
    // 该方法也可直接用于字符串字面值:
    let s = "initial contents".to_string();
    
    // 从 string -> string
    let s = String::from("initial contents");
    
  • 更新string

    let mut s = String::from("foo");
    s.push_str("bar"); // 追加字符串
    s.push('l'); // 追加字符
    
  • 拼接string

    let s1 = String::from("Hello, ");
    let s2 = String::from("world!");
    let s3 = s1 + &s2; // 注意 s1 被移动了,不能继续使用
    
    let s1 = String::from("tic");
    let s2 = String::from("tac");
    let s3 = String::from("toe");
    let s = format!("{}-{}-{}", s1, s2, s3); // 多个字符串连接
    
  • 索引string

    let s1 = String::from("hello");
      //let s1 ="sdfaasdf";
    let h = s1[0]; // 错误!不能这样索引
      println!("{}, {}", &hello[0..1], hello);
    
  • 遍历string

    let s = String::from("hello world!");
    for c in s.chars() {
    		println!("{}", c); // 遍历每一个char,结果是字符
    }
    for c in s.bytes() {
    		println!("{}", c); // 遍历每一个byte,结果是u8类型的数字
    }
    
  • string 和 int 互换

     let int_value = 5;
      let string_value = int_value.to_string();//int to String
      let back_int = string_value.parse::().unwrap();//类型可以换乘下面的 u32 、i16 等
      //let back_int = string_value.parse::().unwrap();
      //let back_int = string_value.parse::().unwrap();
    
  • string 的常用方法:
    参考这里

    方法 原型 说明
    new() pub const fn new() -> String 创建一个新的字符串对象
    to_string() fn to_string(&self) -> String 将字符串字面量转换为字符串对象
    replace() pub fn replace<'a, P>(&'a self, from: P, to: &str) -> String 搜索指定模式并替换
    as_str() pub fn as_str(&self) -> &str 将字符串对象转换为字符串字面量
    push() pub fn push(&mut self, ch: char) 再字符串末尾追加字符
    push_str() pub fn push_str(&mut self, string: &str) 再字符串末尾追加字符串
    len() pub fn len(&self) -> usize 返回字符串的字节长度
    trim() pub fn trim(&self) -> &str 去除字符串首尾的空白符
    split_whitespace() pub fn split_whitespace(&self) -> SplitWhitespace 根据空白符分割字符串并返回分割后的迭代器
    split() pub fn split<'a, P>(&'a self, pat: P) -> Split<'a, P> 根据指定模式分割字符串并返回分割后的迭代器。模式 P 可以是字符串字面量或字符或一个返回分割符的闭包
    chars() pub fn chars(&self) -> Chars 返回字符串所有字符组成的迭代器

14.3 HashMap

HashMap 由 键-值组成,键类型是string,值类型是i32;

  • 新建HashMap
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10); // Blue 是键,10 是值
    scores.insert(String::from("Yellow"), 50);
    
    利用 vector 生成 HashMap:(其实是 vector -> 元祖 -> HashMap,这两个阶段用到了 zip 和 collect() 方法)
    use std::collections::HashMap;
    let teams = vec![String::from("Blue"), String::from("Yellow")];
    let initial_scores = vec![10, 50];
    let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); // HashMap<_, _> 类型注解不能少
    
    也可以用插入 vector 的方法生成 HashMap:
    use std::collections::HashMap;
    let field_name = String::from("Favorite color");
    let field_value = String::from("Blue");
    let mut map = HashMap::new();
    map.insert(field_name, field_value);
    // 这里 field_name 和 field_value 不再有效,所有权交给了map
    
  • 访问 HashMap 中的值
    可以通过 get 方法并提供对应的键来从哈希 map 中获取值,如果在里面没有对用的值,get 会返回 None:
    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); // 根据健找值
    for (key, value) in &scores {
    		println!("{}: {}", key, value); // 访问 HashMap 中的每一对键值
    }
    
  • 更新 HashMap
    1 覆盖一个值
    如果我们插入了一个键值对,接着用相同的键插入一个不同的值,与这个键相关联的旧值将被替换:
    use std::collections::HashMap;
    let mut scores = HashMap::new();
    scores.insert(String::from("Blue"), 10);
    scores.insert(String::from("Blue"), 25);
    println!("{:?}", scores); //这会打印出{"Blue": 25},原始的值10则被覆盖了。
    
    2 只在键没有对应值时插入
    如果 HashMap 中不存在这个键值就插入,否则忽略:
    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); // 打印出 {"Yellow": 50, "Blue": 10}
    
    3 根据旧值更新一个值
    下面代码是计数文本中每一个单词分别出现了多少次:
    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); // 打印结果 {"world": 2, "hello": 1, "wonderful": 1}
    

15 迭代器 Iterarot

Rust 语言中的集合包括 数组( array )、向量( Vect! )、哈希表( map )等,我们可以简单的使用 iter()next() 方法来完成迭代:

fn main() {

   //创建一个数组
   let a = [10,20,30];

   let mut iter = a.iter(); // 从一个数组中返回迭代器
   println!("{:?}",iter);

   //使用 next() 方法返回迭代器中的下一个元素
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
   println!("{:?}",iter.next());
   
   for i in iter {
     // 遍历 iterarot
   }
   for (index, data) in iter.enumerate() {
     // 遍历 iterarot 并携带 索引
   }
}

输出结果:

Iter([10, 20, 30])
Some(10)
Some(20)
Some(30)
None
  • 迭代器类型常用的有3种:

    方法 描述
    iter() 返回一个只读可重入迭代器,迭代器元素的类型为 &T(借用)
    into_iter() 返回一个只读不可重入迭代器,迭代器元素的类型为 T(转移所有权)
    iter_mut() 返回一个可修改可重入迭代器,迭代器元素的类型为 &mut T(可变借用)

使用如下:

let names = vec!["简单教程", "简明教程", "简单编程"];
 for name in names.iter() {
    match name {
       &"简明教程" => println!("我们当中有一个异类!"),
       _ => println!("Hello {}", name),
    }
 }
 println!("{:?}",names); // 迭代之后可以重用集合
}

16 闭包

闭包就是在一个函数内创建立即调用的另一个函数,没有函数名称,闭包不用声明返回值,但它却可以有返回值,闭包有时候有些地方又称之为 内联函数。这种特性使得闭包可以访问外层函数里的变量
闭包格式:

||{
   // 闭包的具体逻辑  
}

创建闭包,并赋给一个变量:

fn main(){
   let is_even = |x| {
      x%2==0
   };
   let no = 13;
   println!("is even ? {}", is_even(no)); // 判断是否为偶数
}

闭包也可以访问外部的变量:

fn main(){
   let val = 10; 
   let closure2 = |x| {
      x + val // 访问作用域外部的变量
   };
   println!("{}",closure2(2));
}

17 指针

17.1 原始引用

Rust 支持两种原始引用:

  • 不可变原始指针 *const T
  • 可变原始指针 *&mut T

as 操作符可以将引用转为原始指针:

let mut x = 10;
let ptr_x = &mut x as *mut i32;
let y = Box::new(20);
let ptr_y = &*y as *const i32;

// 原生指针操作要放在unsafe中执行
unsafe {
    *ptr_x += *ptr_y;
}
assert_eq!(x, 30);

17.2 函数指针

函数指针可以作为函数的参数返回值
作为参数:

pub fn math(op: fn(i32, i32) -> i32, a: i32, b: i32) -> i32{
    op(a, b)/// 通过函数指针调用函数
}
fn sum(a: i32, b: i32) -> i32 {
    a + b
}
let a = 2;
let b = 3;
assert_eq!(math(sum, a, b), 5);

作为返回值:

fn is_true() -> bool { true }
fn true_maker() -> fn() -> bool { is_true } // 函数的返回值是另外一个函数
assert_eq!(true_maker()(), true); // 通过函数指针调用函数

17.3 智能指针

  • Box是指向类型为T的堆内存分配值的智能指针;
  • Box 超出作用域范围时,将调用其析构函数,销毁内部对象,并自动释放内存。
  • 可以通过解引用操作符来获取Box中的T
#[derive(Debug, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}
let box_point = Box::new(Point { x: 0.0, y: 0.0 });
let unboxed_point: Point = *box_point;
assert_eq!(unboxed_point, Point { x: 0.0, y: 0.0 });

y = Box::new(x) // 创建一个指向x的指针
std::mem::drop(x) // 手动提前释放x

Box
Rc允许相同数据有多个所有者;BoxRefCell有单一所有者。
RefCell

18 泛型

18.1 枚举泛型

enum Option {
    Some(T),
    None,
}

18.2 函数泛型

fn takes_anything(x: T) {
    // ...
}

18.3 结构体泛型

struct Point{
    x:T,
    y:T,
}
// 给结构体定义方法
impl  Point{
    fn get_x(&self)->&T{  // 泛型参数都可以使用该方法
        return &self.x;
    }
}

impl Point{
    fn distiance_from_origin(&self)->f32{ // 只有f32类型参数可以使用该方法
        return (self.x.powi(2) + self.y.powi(2)).sqrt();
    }
}

fn main() {
    let int:Point = Point{x:4, y:4};
    let fl:Point  = Point{x:2.0, y:2.0};
    println!("{}, {}, {}, {}", int.x, fl.y, int.get_x(), fl.distiance_from_origin());
}

18.4 trail

trail 用来实现多态。类似C++中的接口;

  • 基本使用

    pub trait Summary{
    	fn summarize(&self)->String; // 在这里可以实现该方法,作为默认方法
    }
    
    pub struct NewsArticle {
    	pub headline: String,
    	pub location: String,
    	pub author: String,
    	pub content: String,
    }
    
    impl Summarizable for NewsArticle { // 有这句话才会将trail应用到结构体中
    	fn summary(&self) -> String {
    		format!("{}, by {} ({})", self.headline, self.author, self.location)
    	}
    }
    
  • trail 作为参数

    // 续"基本使用"的代码
    pub fn notify(item:impl Summary){ //在 notify 函数体中,可以调用任何来自 Summary trait 的方法
    	println!("参数:{}", item.summarize())
    }
    fn main() {
    	let article = NewsArticle {
    	headline: String::from("NewsArticle_headline"),
    	location: String::from("Pittsburgh, PA, USA"),
    	author: String::from("Iceburgh"),
    	content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
       };
    	notify(article);  //值的所有权
    }
    

    此外,Rust还提供了另一种语法糖来,即Trait限定,我们可以使用泛型约束的语法来限定Trait参数;

    pub fn notify(item: T) {
    	println!("Breaking news! {}", item.summarize());
    }
    

    这样的语法糖可以在多个参数的函数中帮助我们简化代码,下面两行代码就有比较明显的对比:

    pub fn notify(item1: impl Summary, item2: impl Summary) {
    pub fn notify(item1: T, item2: T) {
    

    如果某个参数有多个trait限定,就可以使用+来表示

    pub fn notify(item: T) {
    
    /* 或者写成这样更漂亮
    fn some_function(t: T, u: U) -> i32
    	where T: Display + Clone,
    		  U: Clone + Debug
    {*/
    
  • trail 作为返回值

    // 续"基本使用"的代码
    fn returns_summarizable() -> impl Summary {
       NewsArticle {
    	headline: String::from("NewsArticle_headline"),
    	location: String::from("Pittsburgh, PA, USA"),
    	author: String::from("Iceburgh"),
    	content: String::from("The Pittsburgh Penguins once again are the best hockey team in the NHL."),
    	}
    }
    fn main() {
    	let al = returns_summarizable();
    	println!("{}, {}", al.summarize(), al.ari_authon());
    }
    

19 多线程

19.1 创建多线程

创建线程使用的闭包:

use std::thread;
use std::time::Duration;
fn main() {
		let handle = thread::spawn(|| {
				for i in 1..10 {
						println!("hi	number	{}	from	the	spawned	thread!",	i);
						thread::sleep(Duration::from_millis(1));
				}
		});
		for i in 1..5 {
				println!("hi number {} from the main thread!", i);
				thread::sleep(Duration::from_millis(1));
		}
		handle.join().unwrap();
}

输出:

hi	number	1	from	the	main	thread!
hi	number	2	from	the	main	thread!
hi	number	1	from	the	spawned	thread!
hi	number	3	from	the	main	thread!
hi	number	2	from	the	spawned	thread!
hi	number	4	from	the	main	thread!
hi	number	3	from	the	spawned	thread!
hi	number	4	from	the	spawned	thread!
hi	number	5	from	the	spawned	thread!
hi	number	6	from	the	spawned	thread!
hi	number	7	from	the	spawned	thread!
hi	number	8	from	the	spawned	thread!
hi	number	9	from	the	spawned	thread!

19.2 线程与 move 闭包

如果要把外部的数据出传入到线程,那么就需要使用 move 闭包,会把数据的所有权传递给该线程:

use std::thread;
fn main() {
		let v = vec![1, 2, 3];
		let handle = thread::spawn(move || {
				println!("Here's a vector: {:?}", v);
		});
		// v 在这里不能使用了,所有权已传递给线程
		handle.join().unwrap();
}

19.3 消息传递

线程间通信采用的信息传递方式,一个生产者(发送),一个消费者(接收),相当于线程间的通道,下面例子采用 mpsc,mpsc 是多个生产者,单个消费者通道,如:

  • 单线程 - [单发送 - 单接收]
use std::thread;
use std::sync::mpsc;
fn main() {
		let (tx, rx) = mpsc::channel();
		thread::spawn(move || {
				let val = String::from("hi");
				tx.send(val).unwrap();
				// val 在这里就不能使用了,所有权在上一步就传递给了send
		});
		let received = rx.recv().unwrap();
		println!("Got: {}", received);
}
  • 单线程 - [多发送 - 多接收]
use std::thread;
use std::sync::mpsc;
use std::time::Duration;
fn main() {
		let (tx, rx) = mpsc::channel();
		thread::spawn(move || {
				let	vals = vec![
						String::from("hi"),
						String::from("from"),
						String::from("the"),
						String::from("thread"),
				];
				for val in vals {
						tx.send(val).unwrap();
						thread::sleep(Duration::from_secs(1));
				}
		});
		for received in rx {
				println!("Got: {}", received);
		}
}
  • 多线程 - [多发送 - 多接收]
# use std::thread;
# use std::sync::mpsc;
# use std::time::Duration;
#
# fn main() {
// --snip--
let (tx, rx) = mpsc::channel();
let tx1 = mpsc::Sender::clone(&tx);
thread::spawn(move || {
		let vals = vec![
				String::from("hi"),
				String::from("from"),
				String::from("the"),
				String::from("thread"),
		];
		for val in vals {
				tx1.send(val).unwrap();
				thread::sleep(Duration::from_secs(1));
		}
});
thread::spawn(move || {
		let vals = vec![
				String::from("more"),
				String::from("messages"),
				String::from("for"),
				String::from("you"),
		];
		for	val in vals {
				tx.send(val).unwrap();
				thread::sleep(Duration::from_secs(1));
		}
});
for received in rx {
		println!("Got:	{}",	received);
}
// --snip--
# }

19.4 共享状态并发

多个线程拥有数据的所有权

use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
		let counter = Arc::new(Mutex::new(0));
		let mut handles = vec![];
		for _ in 0..10 {
				let counter = Arc::clone(&counter);
				let handle = thread::spawn(move || {
						let mut num = counter.lock().unwrap();
						*num += 1;
				});
				handles.push(handle);
		}
		for handle in handles {
				handle.join().unwrap();
		}
		println!("Result: {}", *counter.lock().unwrap());
}

20 面向对象

参考

21 智能指针

参考

22 测试

22.1 模块测试和集成测试

Rust 的测试分为单元测试集成测试,对应的测试代码只能在 cargo test 命令下编译运行;

  • 单元测试
    单元测试代码一般存在于源码中,测试模块需要用 #[cfg(test)] 修饰,此模块中的测试函数需要用 #[test] 修饰,如:
    pub fn add_two(a: i32) -> i32 {
        a + 2
    }
    #[cfg(test)] //标记该模块为测试模块
    mod tests {
        use super::*;
        #[test] // 标记该函数为测试函数
        // #[should_panic] // 如果出错了也不终止程序
        // #[ignore] // 测试时忽略该函数
        fn add_two_and_two() {
            assert_eq!(4, add_two(2));
        }
    }
    
  • 集成测试
    集成测试需要在项目根目录创建 tests 文件夹,在该文件下创建测试文件 xxx.rs,并在里面编写测试函数,如:
    use adder; // 导入源程序中需要测试的模块
    #[test]
    fn it_adds_two() {
         assert_eq!(4, adder::add_two(2));
    }
    

22.2 测试代码常用关键字

assert!(xxx);                  // 如果值为 true,则通过
assert_eq!(xxx, xxx); // 如果两个值相等,则通过
assert_ne!(xxx ,xxx); // 如果两个值不相等,测通过
panic!("...{}", num);  // 终止程序并打印信息

也可以用 Result 作为函数的返回来编写测试,如:

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() -> Result<(), String> {
         if 2 + 2 == 4 {
              Ok(())
         } else {
              Err(String::from("two	plus	two	does	not	equal	four"))
         }
     }
}

22.3 测试命令

cargo test             // 编译运行测试程序
cargo test [函数名] // 测试某个函数,只要函数名包含该字符串的都会被测试
cargo test -- --test-threads=1 // 单线程测试,因为默认情况是多个测试同时进行
cargo test -- --nocapture // 允许显示其他打印信息,比如 println! ,测试通过的也要打印出信息
cargo test -- --ignored // 让代码中用 #[ignore] 修饰过的函数也参与测试

暂时未分类

交换两个变量

std::mem::swap(&mut a, &mut b); // 相当于交换两者的所有权,数据区域的内存地址没变

退出程序

std::process::exit(0);

将一个 String 变成 &str 用 &* 符号

fn use_str(s: &str) {
    println!("I am: {}", s);
}

fn main() {
    let s = "Hello".to_string();
    use_str(&*s);
}

实现链表的例子

pub struct ListNode {
  pub val: i32,
  pub next: Option>
}

impl ListNode {
fn new(val: i32) -> Self {
    ListNode {
        next: None,
        val
    }
   }
}
pub fn function(l1: Option>) -> Option> {
    let mut res = ListNode::new(0);
    let mut res_temp = &mut res;
    let mut p_l1 = &l1;
    while p_l1.is_some() {
        if let Some(node) = p_l1 {
            p_l1 = &node.next;
        }
        res_temp.next = Some(Box::new(ListNode::new(0)));
        res_temp = res_temp.next.as_mut().unwrap();
    }
    res.next
}

你可能感兴趣的:(#,Rust语言,rust)