Basics

Basics

编译和运行工具

# 单文件编译方法:rustc [filename]

cargo创建新项目目录:

PS cargo new [dir_name]  Created binary (application) [dir_name] package

目录会自动git仓库化,其中包含一个src文件夹和一个toml文件,src目录下有一个main.rs文件。使用cargo new --vcs=git似乎可以避免初始化为git仓库。

TOML文件使用的是专用的TOML格式,用来描述cargo的配置选项。初始有[package]和[dependencies]两个段,package段包含对当前包的信息描述,dependencies段包含这个包的依赖。Rust把包称为crates

src目录用来包含所有的代码文件。

构建运行cargo项目(假设项目名为hello_cargo):

# 目标hello_cargo构建到target/debug/中
PS cargo build  
Compiling hello_cargo v0.1.0 (DIRECTORY\hello_cargo)    
Finished dev [unoptimized + debuginfo] target(s) in 0.56s    

# 一条龙构建运行
PS cargo run 

# 检查可编译性但不生成可执行文件
PS cargo check 

# 构建release文件到target/release/中
PS cargo build --release

https://learnku.com/rust/t/39451

VSCODE中安装一个RUST扩展即可,在设置中修改rust-client.channel为stable;设置editor.formatOnSave为true可以在保存时格式化代码(有rust-fmt的话)

cargo doc --open

Hello world

// use相当于import,如果没有这一句,下面使用io函数的时候就得写成std::io::[somefunc],这一点和C类似
// 语句statement应当以分号(;)结尾
use std::io;​
// main函数的写法:
fn main(){    
    // 函数和宏:名字结尾带一个感叹号的是宏(Rust macro)    
    // 字符串:"something"表示了一个内容为something的字符串对象    
    println!("hello world");        
    // let关键字声明一个variable,其名字为foo,并被绑定到值5上,且不可修改(immutable)
    let foo1 = 5;    
    // 使用mut关键字声明一个可修改的variable    
    let mut foo2 = 3;        
    // String::new是一般意义上的构造函数,返回一个String类型的实例    
    // ::表明new是String的associated function(类似静态函数)    
    // String是可修改的UTF-8文本    
    let mut guess = String::new();        
    // io函数,返回std::io::Stdin实例
    io::stdin()        
        // 此处传参&表示传引用,mut表示是可变变量        
        // 返回io::Result描述结果,是enum变量,可以取值Ok或者Err        
        .read_line(&mut guess)
        // io::Result的expect方法,调用expect会在取值为Err时crash掉并打印传入的错误信息。
        // 这里不调用的话会被编译器警告        
        .expect("Readln failed");
    // 字符串格式化:println!使用格式化输出    
    println!("You guessed: {}", guess);
}​​

引用外部crate时,需要修改toml文件中的[dependency]段。例如我们需要生成随机数,要用到rand crate,于是我们在toml文件中添加:

[dependencies]rand = "0.5.5"

然后执行构建,cargo就会拉取兼容的crates。一旦拉取,cargo不会主动再更新,要把依赖更新到最新版本,需要执行更新指令:

cargo update

然后可以使用随机数生成:

use rand::Rng;

这里的Rng是一个trait,定义了随机数生成相关的方法。

rust允许将同一个变量名多次绑定到不同类型的值上去(类似python),称为shadow

显式指定变量类型的方法为let [varname]: [typename] = [value];

可变性mutability

let x = 5; 
// 默认变量是不可变的。constant的规定更严格一些:const MAX_POINTS : u32 = 100;

shadowing

重复使用 let 以同一变量名创建变量时,新变量覆盖旧变量(或者说该变量名被绑定到了新值上),这种覆盖称为 shadowing。(python 支持类似的机制)shadowing 会随着新变量的销毁(如程序退出新变量的 scope)而失效,此时该变量名将重新绑定到旧值。

let x = 5;
let x = x + 1;

shadow 机制中,同一变量名可以绑定到不同的值类型。而通过将变量声明为 mut 以能够修改其值时,变量的类型是无法改变的。

ownership

  • Each value in Rust has a variable that’s called its owner.

  • There can only be one owner at a time.

  • When the owner goes out of scope, the value will be dropped.

超出作用域scope的变量拥有的内存会直接被drop掉。下面讨论各种情况:

// 5 是一个栈上的基本类型(见下讨论),y 会直接从 x 拷贝这个值,栈上有两个 5
let x = 5;
let y = x;​
// 由于 String 的内容放在堆中,所以s2获得的是指向堆上内容的指针;
// 同时,为了保证退出作用域时不发生double-free,
// s1 对堆上内容的所有权失效,读 s1 会 CE。
// 这个过程相当于一次 move
let s1 = String::from("what");
let s2 = s1;​
// 为了做深拷贝,必须调用 clone 方法
let s1 = String::from("what");
let s2 = s1.clone();

有drop trait的变量无法附加copy trait。

函数传参和返回值时发生的事情类似。

但大多数时候我们希望变量把值传给函数后还能继续使用,如果让函数在返回值时再把所有权通过赋值退回来就太麻烦了。因此 rust 设计了 reference 机制。创建一个 reference 的过程称为 borrowing。

// 引用类型的参数
fn func_name(s: &String) -> usize {
    s.len()
}

fn main() {
    let s1 = String::from("what");
    let len = func_name(&s1); // 传引用
}

为此带来的代价是,borrow来的值默认无法被修改(immutable)。为了能够修改参数,需要把parameter和argument声明为mut的。

fn main() {
    let mut s = String::from("hello");

    change(&mut s);
}

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

可变引用有一些奇怪的规则:

  • 同一块数据在任一时刻最多只能有一个可变引用(持有者本身不算)

    • 如果一个可变引用被销毁了,那之后就可以创建新的可变引用

  • 同一块数据在任一时刻不能同时拥有可变和不可变的引用

  • 同一块数据可以拥有多个不可变引用

基本类型

基本类型分为scalarcompound。这些类型都是在栈上分配的。

有copy trait的类型有所有scalar和tuple。

scalar包括:

  • 整数值(i32, u32等,i表示有符号,u表示无符号,后面的数字表示位数;特别地isizeusize的宽度取决于机器架构)

    • // 类型转换:
      let i: usize = 1;
      a = i as i32;
  • 浮点(f64, f32,默认f64)

  • 布尔值(true,false

  • 字符(rust的字符是unicode编码,4字节宽)

compound包括:

  • tuple

    • 使用圆括号括起多个类型,长度不可变:

    • 可以被destructure,拆到多个变量,这个语法类似于python

    • 还可以用点号来索引tuple的元素

    • 这两种复值方法都不会破坏tuple本身,应该是相当于把值拷贝出来了

    • let tup: (i32, f64) = (50, 0.1);
      let (x, y) = tup;
      let tup: (i32, f64) = (50, 0.1);
      let x = tup.0;
      let y = tup.1;
  • array

    • 使用方括号扩起多个相同类型的值,长度不可变;可变的是vector

    • 数组元素用方括号索引,运行时越界访问会抛出out of bounds异常

    • 循环遍历数组带来的索引合法性检查会影响性能,因此推荐的是使用iter()方法遍历。参见控制流部分。

    • let a = [1,2,3];
      let a: [i32; 3] = [1,2,3]; // 声明数组类型
      let a = [3; 5]; // 含有5个3的数组
      let ele = a[0];
    • 数组可以切片(slice),可参考字符串一节,其类型为&[EleType],切片语法同字符串

基本运算符

1 + 1
1 - 1
1 * 1
1 / 2
1 % 2
1 == 0
1 != 0

函数

rust习惯使用snake case命名函数,即小写加下划线分隔

使用fn关键字可以定义一个函数。函数定义位置的先后不影响调用(和C不同)

严格来说parameter是函数参数,而argument是传入的实际值,不过实际常混用。rust的函数定义方式:

fn func_name(x: i32, y: i32) -> i32 {
	x + y
} // 返回x+y

return语句依然可以使用,一般用于提前返回。

statement和expression

函数定义、变量赋值等都属于statement;

除了一般意义上的表达式外,{ }也是一个表达式。

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

控制流

if表达式(注意是一个表达式expression)实现一个条件分支,条件表达式不需要加括号,其返回值必须是bool变量(而不能是int之类)。

if num < 5 {

} else if {
    
} else {

}

// 条件表达式每个分支的返回值(如果返回了)必须是相同类型,否则CE
let number = if cond { 1 } else { 2 };

loop表达式实现一个无限循环。

loop {
	instructions;
}

使用break可以跳出循环,还可以顺带返回值:

loop {
	instructions;
	break 1; // return 1
}

while结构也是有的:

while cond {
	instructions;
}

for循环遍历区间:

// 1..4是(1..4)的略写(会被编译器提示省略圆括号……),(1..4) 是一个Range类型的实例,左闭右开。这里迭代变量num是不用预先声明的
for num in 1..4 {

}

// 反向遍历
for num in (1..4).rev() {
    
}

for循环可以用来在一个集合结构上迭代,这种迭代比较快速而安全:

let a = [1,2,3];
for ele in a.iter() {
	// ...
}

match语句类似C的switch,将表达式的结果和每个分支的pattern比较。(表达式的括号并不是必要的)

match expression {
	pattern => instructions,
	pattern => {
		instructions;
	}
}

关于match的介绍详见 OOP - match,pattern & if let.

Math

随机数

use rand::Rng;

let number = rand::thread_rng().gen_range(1,101);
// 参数代表区间,左闭右开

比较

use std::cmp::Ordering;

match num1.cmp(&num2) {
	Ordering::Less => [some instructions],
    Ordering::Greater => [],
    Ordering::Equal => [],
}

Last updated

Was this helpful?