当前位置: 首页 > news >正文

rust 程序设计语言入门(1)

本文是阅读《Rust程序设计语言》的学习记录,配合视频《Rust编程语言入门教程》食用更佳

环境搭建

  • windows下载rustup_init.exe,点击安装,默认选择msvc的toolchain,一路default即可

  • 解决下载慢的问题,在powershell中修改环境变量

    $ENV:RUSTUP_DIST_SERVER='https://mirrors.ustc.edu.cn/rust-static'
    $ENV:RUSTUP_UPDATE_ROOT='https://mirrors.ustc.edu.cn/rust-static/rustup'
    
  • 使用vscode作为ide开发,安装rls或ra插件,推荐ra(rust编程语言服务器,承担语法检查等工作)

  • 使用原生的cargo进行new、build和run的操作

  • 下载第三方包速度慢的问题,在~/.cargo/config中换源

    [source.crates-io]
    registry = "https://github.com/rust-lang/crates.io-index"
    replace-with = 'ustc'
    [source.ustc]
    registry = "git://mirrors.ustc.edu.cn/crates.io-index"
    ## registry = "https://mirrors.ustc.edu.cn/crates.io-index"
    
  • 配置文件,包名等信息,添加依赖,由ra自动拉取

    [package]
    name = "hello"
    version = "0.1.0"
    edition = "2018"
    [dependencies]
    rand = "0.3.14"
    
  • vscodetask.jsonlaunch.json配置,参考cargo-tutorial

Hello world

Rust 程序设计语言能帮助你编写更快、更可靠的软件。开发者正在使用Rust在系统和工具领域对当前已有的项目进行重构。Rust的编译器将内存和代码的检查聚焦在程序逻辑方面。不可否认的是,Rust是一门面向未来的语言,在操作系统,嵌入式,音视频分析等领域大展身手。同时,Rust吸收了前辈的诸多优势,正在不断修补并增加新的特性来完善自身。

本文在学习Rust编程指南过程中的一些记录,参考资料为

  • rust-lang/book

  • Rust程序设计语言(中文版)

Rust 是一门预编译(ahead-of-time compiled)语言,可以直接分享和运行编译后的可执行文件

通常使用cargo工具创建Rust项目,具备完善易用的工具链(区别于C/CPP),cargo是Rust的构建系统和包管理器,可以胜任构建代码、下载依赖库,以及编译这些库等任务。

$ cargo new rusttour # 自动创建git项目
$ ll -al
drwxr-xr-x 1 Administrator 197121   0 Feb  7 10:01 .git/
-rw-r--r-- 1 Administrator 197121   8 Feb  7 10:01 .gitignore
-rw-r--r-- 1 Administrator 197121 152 Feb  7 10:01 Cargo.lock
-rw-r--r-- 1 Administrator 197121 177 Feb  7 10:01 Cargo.toml
drwxr-xr-x 1 Administrator 197121   0 Feb  7 10:01 src/
drwxr-xr-x 1 Administrator 197121   0 Feb  7 10:01 target/

Cargo.toml为cargo项目的配置文件,和golang非常类似,使用TOML格式进行配置

$ cargo check # 快速编译检查,比build快得多
$ cargo build
$ cargo run
$ cargo build --release #优化编译

cargo使得不同平台下Rust项目的管理方式统一,便于项目的开发和维护

通用概念

参考官方文档给出的示例程序学习基本的语法概念。如果对其他语言的语法有所接触,会发现导入依赖包和c#相似,而语法和cpp类似。

语法的细节不用过多的纠结(和熟练度相关),只需要关注一些比较特殊的语法即可

  • 导入外部模块(crate)的方式为use,通过::区分名称空间。库 crate 可以包含任意能被其他程序使用的代码,但是不能独自执行。rand为外部依赖,需要在Cargo.tomldependencies中添加
  • 使用println!打印到标准输出
  • 变量的初始化,涉及到Rust的静态强类型系统,具备自动类型推断。
  • 由于是强类型语言因而需要考虑类型转换。类型通过:标识,和python类似的类型标注
  • 创建新的变量类型时可以复用已有的变量名,即类型遮蔽(shadow)
  • 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..101);loop {println!("Please input your guess.");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("Failed to read line");let guess: u32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};println!("You guessed: {}", guess);match guess.cmp(&secret_number) {Ordering::Less => println!("Too small!"),Ordering::Greater => println!("Too big!"),Ordering::Equal => {println!("You win!");break;}}}
}

Rust语法中较为特殊的部分

  • 默认变量不可变,可变需要用mut修饰,但是变量终究和常量(const)不同。

    let x = 10;
    let x = x + 1; // 遮蔽,创建了新的变量
    let mut x = 10; 
    x = x + 1; // 可变
    const y = 10; // 常量
    
  • 数组在栈上分配的已知固定大小的单个内存块,此处可能会产生索引越界错误,panicked at 'index out of bounds'

  • 函数签名中,必须明确声明每个参数的类型。返回值可以是表达式(没有分号)

    fn plus_one(x: i32) -> i32 {x + 1
    }
    
  • 三种循环语句的写法,break的特殊用途(标签跳转,循环语句返回值)

    for element in a {println!("the value is: {}", element);
    }
    

所有权

Rust的所有权系统,使得无需垃圾回收器(garbage collector)即可保证内存安全。“没有手动管理内存的经历可能无法体会Rust所有权系统的良苦用心”

编程语言对于内存的管理无非以下三种

  • 垃圾回收机制
  • 手动分配和释放内存
  • 编译器检查内存管理

Rust采用第三种方式(通过所有权系统管理内存),考虑堆和栈内存的区别。所有权系统的目的是最大限度对堆内存进行管理。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 指针pointer),即分配堆内存。

所有权规则

(1)Rust 中的每一个值都有一个被称为其 所有者owner)的变量。

(2)当所有者(变量)离开作用域,这个值将被丢弃

永远记得在alloc之后使用free释放内存,这通常是由gc来完成的。程序编译过程中可能需要将申请一段未知大小的内存,这段内存使用可能随着程序运行而变化,因而必须在堆中申请。

Rust通过作用域判断变量是否继续使用(自动释放这部分内存),在变量离开作用域是调用drop函数释放内存

(3)值在任一时刻有且只有一个所有者。

以下代码在堆中申请字符串内存,并将指针赋值给变量s1,之后将s1赋值给s2。在main函数执行完毕后,s1和s2失效被丢弃,因而这段内存可能会被连续释放两次(二次释放

Rust 永远也不会自动创建数据的 “深拷贝”,Rust的以上行为被成为移动操作(而非浅拷贝)

let s1 = String::from("hello");
let s2 = s1; // 移动 move
println!("{}, world!", s1); // 报错

如果需要深拷贝,可以使用clone

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

Rust 有一个叫做 Copy trait 的特殊标注,如果一个类型实现了 Copy trait,那么一个旧的变量在将其赋值给其他变量后仍然可用。实现了 Drop trait 的类型就不能再使用 Copy trait。

实现了copy trait的类型有:整型,布尔,浮点,字符和元组(当且仅当元素为前四种)

let x = 5;
let y = x; // 在栈上拷贝数据
println!("x = {}, y = {}", x, y);

当变量为函数的参数时,以上的结论仍旧有效

fn main() {let s = String::from("hello");takes_ownership(s); // s没有实现copy trait,s移动到函数中,之后不可用
}
fn takes_ownership(some_string: String) {println!("{}", some_string);// drop 方法,释放some_string内存
}

当持有堆中数据值的变量离开作用域时,其值将通过 drop 被清理掉,除非数据被移动为另一个变量所有。以下函数中所有权在函数内外转移,如果在函数后仍旧需要使用变量,则必须将变量作为返回值转移出来(否则失效)。这种操作显然很繁琐

fn takes_and_gives_back(a_string: String) -> String {a_string  // 返回 a_string 并移出给调用的函数
}

引用和借用

使用引用,可以使用值但不获取其所有权(意味着变量不会失效),这种操作称为借用

fn main() {let s1 = String::from("hello");let len = calculate_length(&s1); // 传引用println!("The length of '{}' is {}.", s1, len); // string仍可用
}
fn calculate_length(s: &String) -> usize {s.len()// string没有转移
}

如果需要修改引用的值,需要将引用标注为可变的。有以下原则

  • 在同一时间,只能有一个对某一特定数据的可变引用(避免数据竞争产生的未定义行为)

    let r1 = &mut s;
    let r2 = &mut s;
    
  • 无法在拥有不可变引用的同时拥有可变引用(避免写行为影响读操作)

    let mut s = String::from("hello");
    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    let r3 = &mut s; // 大问题
    
  • 一个引用的作用域从声明的地方开始一直持续到最后一次使用为止。意味着只要使用过一次,上面的两个引用即离开作用域,规则刷新。

    let mut s = String::from("hello");
    let r1 = &s; // 没问题
    let r2 = &s; // 没问题
    println!("{} and {}", r1, r2); // 此位置之后 r1 和 r2 不再使用
    let r3 = &mut s; // 没问题
    
  • Rust编译器会检测悬垂指针(指向的内存可能已经被分配给其它持有者)

    fn dangle() -> &String {let s = String::from("hello");&s // 返回字符串 s 的引用,报
    }
    

切片

同样是为了避免已经存在的索引失效,通过使用切片让编译器主动检查

字符串切片时String 中部分值的引用,“字符串 slice” 的类型声明写作 &str,实际上字符串的字面值就是切片。切片也是引用(指向部分数据),默认不可变。

fn main() {let mut s = String::from("hello world");let word = first_word(&s); // 返回切片 &strs.clear(); // s.clear()会获取s的可变引用,从而报错println!("the first word is: {}", word);
}

结构体和枚举

结构体

不得不说各种语言的数据结构都是类似的,Rust的结构体类似于golang的结构体。结构体是一种类型,每部分可以分别对应不同类型。

结构体不允许只将某个字段标记为可变,想要修改成员必须将整个结构体声明为可变

fn main() {let mut user1 = User {email: String::from("someone@example.com"),username: String::from("someusername"),active: true,sign_in_count: 1,};user1.email = String::from("anotheremail@example.com");
}

复用其他的结构体,.. 语法指定了剩余未显式设置值的字段应有与给定实例对应字段相同的值

fn main() {let user2 = User {email: String::from("another@example.com"),..user1};
}

为了方便调试,直接使用println!宏需要结构体实践display trait。但是可以指定debug模式显示更多信息

#[derive(Debug)] // 指定结构体实现Deubg trait
struct Rectangle {width: u32,height: u32,
}
fn main() {let rect1 = Rectangle {width: 30,height: 50,};println!("rect1 is {:?}", rect1);// println!("rect1 is {:#?}", rect1);
}

继续看结构体的方法实现,类似于golang和python

impl Rectangle {fn area(&self) -> u32 { // &self 实际上是 self: &Self 的缩写self.width * self.height}fn can_hold(&self, other: &Rectangle) -> bool { // 多个参数self.width > other.width && self.height > other.height}
}

在vscode中借助插件能够看到结构体目前实现了两个implementation

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7OUh1nt7-1676200228390)(assets/image-20230208145057388.png)]

不指定self为第一个参数的称为关联函数,使用结构体名和 :: 语法来调用这个关联函数

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

枚举

枚举类型能够增强代码的可读性,咋一看和结构体类似。但是枚举能够在同一种类型中定义不同的结构,尽管可以用4个结构体(4种类型)实现和以下相同的逻辑,但是无疑增加了复杂度。

enum Message {Quit,Move { x: i32, y: i32 },Write(String),ChangeColor(i32, i32, i32),
}

同样可以为枚举实现方法

impl Message {fn call(&self) {// 在这里定义方法体}
}

在标准库中Option枚举类型处理空值(空指针)。空值Null )是一个值,它代表没有值。在有空值的语言中,变量总是这两种状态之一:空值和非空值。

Option枚举类型被包含在了 prelude(预导入模块),可以不需要 Option:: 前缀来直接使用 SomeNone

在对 Option<T> 进行 T 的运算之前必须将其转换为 T确保该变量不为空,确保代码的安全性

enum Option<T> {Some(T),None,
}

包管理和crate

Rust的包管理主要涉及到以下模块系统

  • Packages): Cargo 的一个功能,它允许你构建、测试和分享 crate。
  • Crates :一个模块的树形结构,它形成了库或二进制项目。
  • 模块Modules)和 use: 允许你控制作用域和路径的私有性。
  • 路径path):一个命名例如结构体、函数或模块等项的方式

回顾下cargo new 创建的新项目结构,如果src/main.rs 存在则是二进制crate,如果 src/lib.rs 存在则是库crate。根crate和包的名称相同

paakage的要点

  • 包含一个cargo.toml,描述如何构建crate
  • 只能包含0或1个库crate
  • 可以包含任意数量binary create(在src/bin路径下)

惯例

  • binary create 的 create root 为 /src/main.rs,编译的入口,和package 名称相同
  • library create 的 create root 为 /src/lib.rs,编译的入口,和package 名称相同
image-20230208161159032

将mod拆分为多个文件

file not found for module `hosting`
to create the module `hosting`, create file "src\front_of_house\hosting.rs" or "src\front_of_house\hosting\mod.rs"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Pufesco9-1676200228396)(assets/image-20230208163540664.png)]

错误处理

Rust将错误分为可恢复的错误(Result)与panic

默认情况下Rust会进行调用栈回溯,清理调用栈数据会有性能损耗,也可以 配置不需要清理调用栈(交给os清理)

[profile.release]
panic = 'abort'

当主动触发panic时,报错会提示开启backtrace,例如宏 panic!("crash and burn");,只有在debug模式下才能看到栈的回溯信息

thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: Run with `RUST_BACKTRACE=1` for a backtrace.

其他语言中通过try…catch捕获异常,而Rust则具备Result枚举类型(同样是预导入的)对结果进行模式匹配。Result有两个成员Ok和Err例如

let f = File::open("hello.txt");
let f = match f {Ok(file) => file,Err(error) => match error.kind() {ErrorKind::NotFound => match File::create("world.txt") {Ok(fc) => fc,Err(e) => panic!("Problem creating the file: {:?}", e),},other_error => panic!("Problem opening the file: {:?}", other_error),}
};

当异常嵌套时,match语法会变得难以理解,因而有unwrap和expect的写法(实际上和match结果相同)

  • 如果 Result 值是成员 Okunwrap 会返回 Ok 中的值。如果 Result 是成员 Errunwrap 会为我们调用 panic!

  • expect和unwrap的用法一致,但是在触发panic!可以指定error信息

let f = File::open("hello.txt").unwrap();
let f = File::open("hello.txt").expect("Failed to open hello.txt");

此外,错误可以不在当前作用域处理,而是向上传播(将Result作为函数返回值),仍旧可以用?简写

  • 如果 Result 的值是 Ok,这个表达式将会返回 Ok 中的值而程序将继续执行。
  • 如果值是 ErrErr 将作为整个函数的返回值,就好像使用了 return 关键字一样
let mut f = match f {Ok(file) => file,Err(e) => return Err(e),
};
let mut f = File::open("hello.txt")?; // 和上面的代码一致

泛型和trait

泛型太常见了,主要目的是使用泛型来编写不重复的代码,听说隔壁golang也要支持泛型了.

主要考虑在以下方式使用的泛型

  • 函数
  • 结构体
  • 枚举
  • 方法

Rust 实现了泛型,使得使用泛型类型参数的代码相比使用具体类型并没有任何速度上的损失

Rust 通过在编译时进行泛型代码的 单态化monomorphization)来保证效率。单态化是一个通过填充编译时使用的具体类型,将通用代码转换为特定代码的过程

trait 告诉Rust编译器某种类型具有哪些可以与其他类型共享的功能

  • trait bounds 指定泛型是任何拥有特定行为的类型
  • trait和接口(interface)的概念类似但是有所区别

不同类型可能具有相同的方法,称为共享相同行为,原则如下

  • 只有方法签名没有实现
  • trait有多个方法,每个独占一行
  • 实现trait的类型需要提供方法实现(在trait中没有默认实现时),如果有默认实现会进行方法重载
  • 类型或trait必须至少有一个是本地crate中定义的
  • 无法为外部类型实现外部trait(为标准库vec实现标准库的display trait)

以上的定义实际上和接口一致,下面的各种概念可以直接联系接口理解

// src/lib.rs
pub trait Summary {fn summarize1(&self) -> String;fn summarize2(&self) -> String { // 默认实现String::from("(Read more...)")}
}
impl Summary for NewsArticle {fn summarize1(&self) -> String {format!("{}, by {} ({})", self.headline, self.author, self.location)}
}

有了trait我们可以实现多态了,方法的类型为实现的trait,参数可以将实现该trait的类型传入

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

trait实现适用于较为简单的情景,较为复杂的情景可以使用语法糖(trait boundary)简化,以下代码和上面的代码意义相同

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

对于实现多个trait的情形,trait boundary 的写法如下,参数同时实现了Summary和Display

pub fn notify(item: impl Summary + Display) {
pub fn notify<T: Summary + Display>(item: T) {
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {

对于上面的第三行中写法较为冗长,可以继续通过语法糖简化,类似C#的写法

fn some_function<T, U>(t: T, u: U) -> i32
where T: Display + Clone,U: Clone + Debug
{

将trait作为参数返回 ,函数的返回类型只能为同一种(即使实现了同样的trait )

fn returns_summarizable() -> impl Summary {

甚至可以在有条件指定泛型方法的实现,以下只有实现了Display + PartialOrd的类型才有cmp_display方法

impl<T: Display + PartialOrd> Pair<T> {fn cmp_display(&self) {

总结一下,trait 和 trait bound 让我们使用泛型类型参数来减少重复,并仍然能够向编译器明确指定泛型类型需要拥有哪些行为。

在动态类型语言中,如果我们尝试调用一个类型并没有实现的方法,会在运行时出现错误。Rust 将这些错误移动到了编译时,甚至在代码能够运行之前就强迫我们修复错误

生命周期

https://rustwiki.org/zh-CN/book/ch10-03-lifetime-syntax.html

生命周期的概念时Rust与众不同的地方之一,较为难以理解(需要更多例子说明),Rust每个引用都有自己的生命周期(在作用域内保持有效)。总的来说感觉是通过复杂的语法标注减少安全隐患

大多数情况下生命周期都能够隐式推断,在特定情况下需要手动标注生命周期

生命周期存在的目的是避免悬垂引用(指向已经释放的内存)

Rust借用检查器会判断引用的生命周期(作用域范围)是否小于被借用的对象,如果不小于说明对象已经释放但是借用仍然存在,出现悬垂引用。

let r: &i32;
{let x = 5;r = &x;
} // r的生命周期大于x,出现悬垂引用
println!("r: {}", r);

更复杂的例子例如,函数返回参数借用,但是不确定返回x和y,x和y的生命周期不一定是相同的,所以无法推断需要手动标注

fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}

生命周期标注并不会改变引用的生命周期,只是描述多个引用之间的生命关系

// 泛型生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { // 表示参数和返回值的生命周期一致// 泛型中的'a, 表明任何标注了'a的引用,其生命周期都不能短于'a// 任何不满足这个约束条件的值都将被借用检查器拒绝// 借用检查器并不知道具体引用的生命周期,只是检查是否符合约束条件// 整个的检查都是在编译阶段完成的if x.len() > y.len() {x} else {y}
}

对于以下示例,尽管string1的生命周期满足result的要求,但是string2的生命周期在打印result之前就已经结束,由于生命周期会取两者重叠的部分(较小值)作为返回引用的生命周期,因此借用检查器不允许编译通过,显示错误string2 does not live long enough,borrowed value does not live long enough

fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");result = longest(string1.as_str(), string2.as_str());}println!("The longest string is {}", result);
}

因此,逻辑上只对x参数进行标注即可,但是由于y同样是返回引用,因此编译器需要标注生命周期,编译器报错^ lifetime 'a required。修改为以下可以通过编译

fn longest<'a>(x: &'a str, y: &str) -> &'a str { x
}

对于结构提来说,引用可以是成员,为了避免悬垂需要保证成员的生命周期不小于结构体本身

struct ImportantExcerpt<'a> {part: &'a str,
}

生命周期省略的规则在一定程度上减轻了编码者的心智负担。例如对于方法定义来说,self参数的生命周期会自动赋给所有输出参数的生命周期。规则如下

  • 每一个是引用的参数都有它自己的生命周期参数
  • 如果只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
  • 如果方法有多个输入生命周期参数并且其中一个参数是 &self&mut self,那么所有输出生命周期参数被赋予 self 的生命周期

静态生命周期('static)是一个特殊的生命周期,在程序的持续时间内都有效,例如字符串生命值,但是这个要慎用

下面有一个综合泛型,trait和生命周期的例子

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a strwhere T: Display
{println!("Announcement! {}", ann);if x.len() > y.len() {x} else {y}
}

最终总结就是

  • 返回的引用必须和某个参数的生命周期匹配
  • 生命周期标注不影响生命周期,只是静态的检查
  • 函数生命周期的目的在于描述参数和返回值之间的关系,一旦有联系那么必须通过明确标注的方式进行描述
  • 结构体引用成员的生命周期不小于结构体实例本身

可见为了避免悬垂引用的问题,编译器将压力给到了编码者,尽管有生命周期省略的规则,但是负担仍旧很重

单元测试

测试函数的三个操作

  • 准备数据
  • 运行被测试的代码
  • 断言结果

Rust 中的测试就是一个带有 test 属性标注的函数。属性(attribute)是关于 Rust 代码片段的元数据

使用assert!宏,可以额外指定自定义信息

assert! 
assert_eq! // 失败时自动使用debug格式打印参数,要求参数实现了PartialEq和Debug trait
assert_ne!

示例如下

pub fn add_tow(a: i32) -> i32 {a + 2
}#[cfg(test)]
mod tests {use crate::add_tow;#[test]fn if_add_twos() {assert_eq!(4, add_tow(2));}#[test]fn if_add_twos_info() {let result =  add_tow(2);// assert!(5 == result,"failed to add two for 3, get wrong value {}",result);assert_eq!(5,result,"failed to add two for 3, get wrong value {}",result);}
}

测试代码预期的panic行为

#[test]
#[should_panic]
// #[should_panic(exepcted = "should include this txt")] // 检测发生panic具体的panic内容匹配
fn test_panic() {panic!("shoule panic");// panic!("should include this txt");
}

在测试中,也可以通过返回Result枚举的方式作为返回类型编写测试

  • 返回OK,测试通过
  • 返回Err,测试失败
#[test]
fn test_result() -> Result<(),String>{if 1 == 1 {Ok(())} else {Err(String::from("error info"))}
}

控制测试的运行逻辑

cargo test会构建一个Test Runner的可执行文件,逐个运行测试函数并报告结果

Cargo测试的默认行为

  • 并行测试
  • 执行所有测试
  • 捕获(不显示)所有输出
cargo test -- --test_threads=1 // 并行测试的线程数
cargo test -- --show-output // 默认通过的测试不会打印pringln!宏的内容
cargo test 

运行忽略的测试

cargo test -- --ignored // 执行被忽略标记的测试
// lib.rs
#[test]
#[ignore] // 忽略耗时测试
fn test_panic() {panic!("shoule panic");// panic!("should include this txt");
}

#[cfg(test)] 单元测试标注测试mod,只有执行test操作才会编译此部分

测试模块的#[cfg(test)] 标注告诉 Rust 只在执行 cargo test 时才编译和运行测试代码,而在运行 cargo build 时不这么做

Rust允许测试私有函数,未导出

对于集成测试,在项目根目录下创建test文件夹,每个文件都被视作单独的crate

可以创建common文件夹作为集成测试的公用逻辑,不在测试范围内

注意:

  • 如果是binary create ,则不能在 tests 目录下创建集成测试,无法把main.rs导入作用域
  • 只有 library crate 才能暴露函数给其他crate用

最后,总结下,Rust将程序安全相关的问题从运行时提前到了编译期间,迫使开发者思考程序中可能存在的不安全因素

  • 默认不可变即只读的变量,写需要单独声明
  • 尽可能避免无效,未定义内存的使用
  • 尽可能避免数据争夺和未定义行为(悬垂指针,空指针
  • 主动触发panic(可恢复错误),避免缓冲区溢出(例如索引越界)

相关文章:

rust 程序设计语言入门(1)

本文是阅读《Rust程序设计语言》的学习记录&#xff0c;配合视频《Rust编程语言入门教程》食用更佳 环境搭建 windows下载rustup_init.exe&#xff0c;点击安装&#xff0c;默认选择msvc的toolchain&#xff0c;一路default即可 解决下载慢的问题&#xff0c;在powershell中修…...

基于蜣螂算法改进的LSTM预测算法-附代码

基于蜣螂算法改进的LSTM预测算法 文章目录基于蜣螂算法改进的LSTM预测算法1.数据2.LSTM模型3.基于蜣螂算法优化的LSTM4.测试结果5.Matlab代码摘要&#xff1a;为了提高LSTM数据的预测准确率&#xff0c;对LSTM中的参数利用蜣螂搜索算法进行优化。1.数据 采用正弦信号仿真数据&…...

Python安全开发——Scapy流量监控模块watchdog

目录 Python蓝队项目说明 (一)Python-蓝队项目-Scapy流量分析 0x01 Scapy参数介绍...

阶段二5_集合ArrayList

一.对象数组 1.对象数组使用案例 需求&#xff1a;将&#xff08;张三&#xff0c;23&#xff09;&#xff08;李四&#xff0c;24&#xff09;&#xff08;王五&#xff0c;25&#xff09; 封装为3个学生对象并存入数组 随后遍历数组&#xff0c;将学生信息输出在控制台 思路…...

十一、Python——匿名函数

1.匿名函数:简化函数定义 2.格式 lambda 参数1&#xff0c;参数2…&#xff1a;运算 3.匿名函数特点 不需要指明函数名定义只有一条语句函数体必须是一个表达式不能显示使用return 4.匿名函数实现求和 s lambda a,b:a b result s(1,2) print(result) # 35.匿名函数作…...

数组常使用的方法

1. join (原数组不受影响)该方法可以将数组里的元素,通过指定的分隔符,以字符串的形式连接起来。返回值:返回一个新的字符串const arr[1,3,4,2,5]console.log(arr.join(-)&#xff1b;//1-3-4-2-52. push该方法可以在数组的最后面,添加一个或者多个元素结构: arr.push(值)返回值…...

2023华为软件测试笔试面试真题,抓紧收藏不然就看不到了

一、选择题 1、对计算机软件和硬件资源进行管理和控制的软件是&#xff08;D&#xff09; A.文件管理程序 B.输入输出管理程序 C.命令出来程序 D.操作系统 2、在没有需求文档和产品说明书的情况下只有哪一种测试方法可以进行的&#xff08;A&#xff09; A.错误推测法测…...

洛谷2月普及组(月赛)

&#x1f33c;小宇&#xff08;治愈版&#xff09; - 刘大拿 - 单曲 - 网易云音乐 OI赛制且难度对标蓝桥杯省赛&#xff08;&#x1f625;真难&#xff0c;第三题做了几百年&#xff0c;第四题只敢骗骗分&#xff09; 花了10块钱&#x1f643; 买官网的思路&#xff0c;结果…...

【博学谷学习记录】超强总结,用心分享 | 架构师 Spring源码学习总结

文章目录Spring的循环依赖1.循环依赖的定义&&原因2.循环依赖的场景1.构造器注入引起循环依赖2.Field属性setter注入的循环依赖3.循环依赖解决思路4.三级缓存5.面试题[三级缓存]AOP源码深度剖析概述Spring AOP的前世今生实现机制**JDK 动态代理****CGLIB 代理**流程总结…...

Linux C/C++ timeout命令实现(运行具有时间限制)

Linux附带了大量命令&#xff0c;每个命令都是唯一的&#xff0c;并在特定情况下使用。Linux timeout命令的一个属性是时间限制。可以为任何命令设置时间限制。如果时间到期&#xff0c;命令将停止执行。 如何使用timeout命令 我们将解释如何使用Linux timeout命令 timeout […...

西湖论剑初赛web wp

Node Magical Login 简单的js代码审计。 Flag分成了两部分。 第一部分&#xff1a; 这里就简单的判断了一下user是否等于admin&#xff0c;直接绕过。 第二部分&#xff1a; checkcode ! “aGr5AtSp55dRacer”&#xff0c;让其为真&#xff0c;利用数组绕过。 Flag为&#x…...

【YOLOv8/YOLOv7/YOLOv5系列算法改进NO.55】融入美团最新QARepVGG

文章目录 前言一、解决问题二、基本原理三、​添加方法四、总结前言 作为当前先进的深度学习目标检测算法YOLOv8,已经集合了大量的trick,但是还是有提高和改进的空间,针对具体应用场景下的检测难点,可以不同的改进方法。此后的系列文章,将重点对YOLOv8的如何改进进行详细…...

Flutter Windows端打包并生成可安装文件流程

Windows打包 1.首先安装visual Studio 下载地址&#xff1a;https://visualstudio.microsoft.com/zh-hans/ 下载成功后按照下图勾选桌面应用和移动应用下的使用C的桌面开发&#xff0c;勾选右侧安装详细信息中的windows 11/10 sdk 中的任意一个完成安装即可 2.打包Windows …...

凸优化学习:PART3凸优化问题(持续更新)

凸优化问题 凸优化问题的广义定义&#xff1a; 目标函数为凸函数约束集合为凸集 一、优化问题 基本用语 一般优化问题的描述&#xff1a; minimize⁡f0(x)subject to fi(x)⩽0,i1,⋯,mhi(x)0,i1,⋯,p(1)\begin{array}{ll} \operatorname{minimize} & f_0(x) \\ \text { s…...

[ue4] 着色器绑定(Shader Binding)

当我们在ue4中制作了一个美术材质之后&#xff0c;引擎本身会为我们做很多事情&#xff0c;它会把结点翻译为hlsl&#xff0c;生成多个shader变体&#xff0c;并在多个mesh pass中去选择性的调用所需的shader&#xff0c;其中一个重要的过程就是获取shader绑定的数据。 本文将主…...

Rust语言之迭代器

文章目录Rust迭代器Rust迭代器的实现Iterator特型IntoIterator特型for循环与迭代器迭代器类型再看for循环实现自定义迭代器方式一方式二相关参考Rust迭代器 Rust语言内置了迭代器模式&#xff0c;用于实现对一个项的序列进行特定的处理&#xff0c;通常配合for循环使用。当我们…...

TreeSet 与 TreeMap And HashSet 与 HashMap

目录 Map TreeMap put()方法 : get()方法 : Set> entrySet() (重) : foreach遍历 : Set 哈希表 哈希冲突 : 冲突避免 : 冲突解决 ---- > 比散列(开放地址法) : 开散列 (链地址法 . 开链法) 简介 : 在Java中 , TreeSet 与 TreeMap 利用搜索树实现 Ma…...

Java围棋游戏的设计与实现

技术&#xff1a;Java等摘要&#xff1a;围棋作为一个棋类竞技运动&#xff0c;在民间十分流行&#xff0c;为了熟悉五子棋规则及技巧&#xff0c;以及研究简单的人工智能&#xff0c;决定用Java开发五子棋游戏。主要完成了人机对战和玩家之间联网对战2个功能。网络连接部分为S…...

第七十三章 使用 irisstat 实用程序监控 IRIS - 使用选项运行 irisstat

文章目录第七十三章 使用 irisstat 实用程序监控 IRIS - 使用选项运行 irisstat使用选项运行 irisstatirisstat Options第七十三章 使用 irisstat 实用程序监控 IRIS - 使用选项运行 irisstat 使用选项运行 irisstat 不带选项运行 irisstat 会生成基本报告。通常&#xff0c;…...

【博客619】PromQL如何实现Left joins以及不同metrics之间的复杂联合查询

PromQL如何实现Left joins以及不同metrics之间的复杂联合查询 1、场景 我们需要在PromQL中实现类似SQL中的连接查询&#xff1a; SELECT a.value*b.value, * FROM a, b2、不同metrics之间的复杂联合查询 瞬时向量与瞬时向量之间进行数学运算&#xff1a; 例如&#xff1a;根…...

Win11自定义电脑右下角时间显示格式

Win11自定义电脑右下角时间显示格式 一、进入附加设置菜单 1、进入控制面板&#xff0c;选择日期和时间 2、选择修改日期和时间 3、选择修改日历设置 4、选择附加设置 二、自定义时间显示出秒 1、在选项卡中&#xff0c;选时间选项卡 2、在Short time的输入框中输入H:m…...

TrueNas篇-trueNas Scale安装

安装TrueNAS Scale 在尝试trueNas core时发下可以成功安装&#xff0c;但是一直无法成功启动&#xff0c;而且国内对我遇见的错误几乎没有案例&#xff0c;所以舍弃掉了&#xff0c;而且trueNas core是基于Linux的&#xff0c;对Linux的生态好了很多&#xff0c;还可以可以在t…...

element表单搜索框与表格高度自适应

一般在后台管理系统中&#xff0c;表单搜索框和表格的搭配是非常常见的&#xff0c;如下所示&#xff1a; 在该图中&#xff0c;搜索框有五个&#xff0c;分为了两行排列。但根据大多数的UI标准&#xff0c;搜索框默认只显示一行&#xff0c;多余的需要进行隐藏。此时的页面被…...

MySQL使用技巧整理

title: MySQL使用技巧整理 date: 2021-04-11 00:00:00 tags: MySQL categories:数据库 重建索引 索引可能因为删除&#xff0c;或者页分裂等原因&#xff0c;导致数据页有空洞&#xff0c;重建索引的过程会创建一个新的索引&#xff0c;把数据按顺序插入&#xff0c;这样页面…...

七大设计原则之里氏替换原则应用

目录1 里氏替换原则2 里氏替换原则应用1 里氏替换原则 里氏替换原则&#xff08;Liskov Substitution Principle,LSP&#xff09;是指如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都替换成 o2 时&#xff0c;程序 P…...

1行Python代码去除图片水印,网友:一干二净

大家好&#xff0c;这里是程序员晚枫。 最近小明在开淘宝店&#xff08;店名&#xff1a;爱吃火锅的少女&#xff09;&#xff0c;需要给自己的原创图片加水印&#xff0c;于是我上次给她开发了增加水印的功能&#xff1a;图片加水印&#xff0c;保护原创图片&#xff0c;一行…...

Connext DDS属性配置参考大全(2)

DDSSecure安全com.rti.servcom.rti.serv.load_plugin...

一起Talk Android吧(第四百九十二回:精简版动画)

文章目录概念介绍使用方法示例代码经验总结各位看官们大家好&#xff0c;上一回中咱们说的例子是"动画集合&#xff1a;AnimatorSetBuilder",这一回中咱们说的例子是" 精简版动画"。闲话休提&#xff0c;言归正转&#xff0c;让我们一起Talk Android吧&…...

seata源码-全局事务回滚服务端源码

这篇博客来记录在发起全局事务回滚时&#xff0c;服务端接收到netty请求是如何处理的 1. 发起全局事务回滚请求 在前面的博客中&#xff0c;有说到过&#xff0c;事务发起者在发现分支事务执行异常之后&#xff0c;会提交全局事务回滚的请求到netty服务端&#xff0c;这里是发…...

【Vue3源码】第一章 effect和reactive

文章目录【Vue3源码】第一章 effect和reactive前言1、实现effect函数2、封装track函数&#xff08;依赖收集&#xff09;3、封装reactive函数4、封装trigger函数&#xff08;依赖触发&#xff09;5、单元测试【Vue3源码】第一章 effect和reactive 前言 今天就正式开始Vue3源码…...