rust学习笔记 1
start at 2023/03/19.

我多多少少也学了不少语言了,按顺序来:Lua -> Python -> VB -> C -> C++ -> Java -> Go -> html/js/css,其他还有杂七杂八的非主流语言也多多少少了解过有点,虽然都不算特别精通,毕竟还没有工作,所以都是了解基本的语法,没有那些能理解每种语言痛点的觉悟,不能提取每种语言的哲学。各种语言中都有相通的地方,理解了这些相通的地方之后,学习一门新的语言就都不难,一般流程都是学完基本的语法之后熟悉一些第三方库即可,这次学习 rust 并不是跟风啊,我也不知道这个邪教语言好在哪里,也不指望有什么 rust 岗位,只是觉得总该学一门来写以后爱好的项目,其实 C 是一个很好的选择,但我不想现在去深入,然后是 Linus 允许使用 rust 写 linux 内核,我就开始对这门语言有点好奇了

胡言乱语

rust 语法风格类似 javascript, 编译结果类似 C,编译出的程序高效,有 cargo 包管理

rust 的应用领域:

  • 命令行(可执行文件)
  • web:编译成 WebAssembly, javascript 替代品,速度提升很大
  • 服务器程序
  • 嵌入式

manjaro 配置环境

我忘了怎么装 rust 的了,记起来了再说,好像是装了 rustup

Hello, world

rust 的文件后缀名为 .rs

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

写完之后编译,运行

$ rustc hello.rs
$ ./hello
Hello, world!

命令行程序相关

格式化输出

格式化使用的是和 python 里的 format 类似的 {}

fn main() {
    let a = 12;
    println!("Hello, {}!", a);
}

可以指定使用后面跟着的第几个

fn main() {
    let a = 12;
    println!("Hello, {0}{1}{2}!", a, a, a);
}

如果你想要输出 {} 的话就可以使用以下语句输出

println!("{{}}");

详细输出

#[derive(Debug)]

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

fn main() {
    let rect = Rect{ width: 30, height: 50 };
    println!("{:?}", rect);
    println!("{:#?}", rect);
}

最上面那一行是必要的

输入

直接输出:

use std::io::stdin;

fn main() {
    let mut str_buf = String::new();
    stdin().read_line(&mut str_buf).unwrap();
    println!("Your input line is \n{}", str_buf);
}

从输入中读取两个整数,完成求和

use std::io::stdin;

fn main() {
    let mut str_buf = String::new();
    stdin().read_line(&mut str_buf).unwrap();
    let sp: Vec<&str> = str_buf.as_str().split(' ').collect();
    let a = sp[0].trim().parse::<i32>().unwrap();
    let b = sp[1].trim().parse::<i32>().unwrap();
    println!("{} + {} = {}", a, b, a + b);
}

语法看上去可能有点奇怪,之后应该会具体讲解

从命令读取参数

fn main() {
    let args = std::env::args();
    for arg in args {
        println!("{}", arg);
    }
}

语法

变量赋值

let a = 123;
let b = 4.3;
let c: i32 = 132;

第一行的赋值方式是编译器在编译的时候自动根据 123 的值将 a 识别成 i32,类似 go 中的海象符,区别是加一个 javascript 中的 let

rust 中的类型是强制的,下面这段代码会报错

let a = 123;
let b = 4.5;
println!("{}", a + b);

a 是 i32 类型的变量,b 是 f64 类型的变量,在 C 语言中就会在这时候将 int 转换成 float 类型进行计算,但是 rust 中就不允许

另外还有一个与众不同的地方 变量在默认状态下不可以被赋值改变,这是因为在大量的编程实践中,人们发现开发者往往会用变量存储一个不可变的值,仅仅是为了将这个值存储一下以供他用,但这些不可变的变量往往极大地影响并发程序中对变量的使用,因此 rust 默认状态下保护了所有的变量

变量也不必在定义之后立即赋值,但是它默认状态下只能赋值一次

let a;
a = 456;

如果你想要一个可以改变的变量值,就可以使用 mut 关键字声明

fn main() {
    let mut a = 123;
    a = a + 3;
    println!("{}", a);
}

同一个变量名可以被多次赋值,这和可变变量赋值的性质有点不一样,它是可以改变类型的,有点像 python 中的变量,这一行为被称为重影

let x = 32;
let x = x + 4;
let x = "233";

常量赋值

const HELLO: i32 = 233;

虽然看上去 不可变量 的概念和常量十分相似,但是实际上是有所不同的:

  • 常量声明必须指定类型
  • 常量必须在定义时赋值
  • 常量可以在函数内外声明,但是使用 let 声明的变量只能在函数内使用

静态变量

静态变量类似全局变量,在 C 语言中,全局变量就是指放在程序内存的静态区中,不随函数的调用或结束而存在或消亡,只在进程开始的时候被创建,进程结束的时候被回收,而静态变量就是这样的东西,但是静态变量的概念不等于全局变量,静态变量是可以在函数中声明的,在 rust 中可以使用如下的方法声明一个静态变量,static 替换掉了原来 let 的位置

let 声明的变量只能在函数内使用

static VAR: i32 = 123;
fn main() {
    static VAR2: i32 = 233;
    println!("{}", VAR);
    println!("{}", VAR2);
}

和常量的声明类似,静态变量声明的时候必须指定类型和初始值,但是静态变量可以像普通变量一样加上 mut 关键字变成可变静态变量

static mut VAR: i32 = 123;

然而对静态变量赋值依然是被认为不安全的行为,如果在多线程程序中,同时有两个线程对这个变量进行操作,就会出现不可预测的情况,所以 rust 不允许直接改变静态变量的值,甚至无法直接调用一个可变的静态变量,如果一定要写这种程序的话你可以在一个不安全的环境里操作和使用这些可变的静态变量

static mut VAR: i32 = 123;
fn main() {
    unsafe {
        VAR = VAR + 3;
        println!("{}", VAR);
    }
}

这是一个不安全的程序

数据类型

整数

分为有符号和无符号,u 开头表示无符号,后面的数字理所当然的是位长度,后面是 size 的数据位长度取决于处理器的架构,如果是 32 位处理器的 isizeusize 类型就是 32 位

let x: i8 = 8;
let x: i16 = 16;
let x: i32 = 32;
let x: i64 = 64;
let x: i128 = 128;
let x: isize = 64;
let x: u8 = 8;
let x: u16 = 16;
let x: u32 = 32;
let x: u64 = 64;
let x: u128 = 128;
let x: usize = 64;

还有进制相关的

let x = 233_333;	// 十进制
let x = 0xfffff;	// 十六进制
let x = 0o77777;	// 八进制
let x = 0b11100;	// 二进制
let x = b'A';		// 字节,只能为 u8 型

整数型中间的 _ 只是用来方便代码阅读的分隔符

浮点数

支持两种浮点型,f32 和 f64,64位浮点数的精度更高

let x: f32 = 3.6;
let x: f64 = 4.5;

布尔

rust 中的 boolean 类型和 python 中的类似,但是不用大写

let a = true;
let b = false;

在 C 语言中,true 等价于 非0 值,而 false 等价于 0, 但是 rust 中这个规则不适用 :anger:

运算

let sum = 5 + 10;
let dif = 4.3 - 2.1;
let pro = 3 * 20;
let quo = 5.4 / 2.2;
let rem = 43 % 5;

rust 中的整数不允许 ++-- 运算,和 python 一样只支持 += 这种符号,因为前者容易让开发者在开发过程中更难意识到变量的值发生了变化

除了这些基本的运算之外,rust 的浮点数还自带了很多数学运算函数

fn main() {
    let x: f64 = 3.0;
    println!("{}", x.sin());
    println!("{}", x.cos());
    println!("{}", x.tan());
    println!("{}", x.sqrt());
    println!("{}", x.powi(3));	// 幂函数
    println!("{}", x.ln());		// 对数
}

字符

let en: char = 'U';
let zh: char = '你';

rust 中的 char 类型大小为 4 字节,代表 Unicode 标量值,所以它可以支持很多符号,但是还是推荐使用字符串存储 UTF-8 文字,中文的编码引起的报错可能是 gbk 编码导致的

字符串

rust 中有两种字符串,一种是 &strString,由于这本书在这一章节并没有说清楚他们之间的关系,所以这里就记一下简单的用法

fn main() {
    let string = String::from("Some string");
    let mut string = String::from("");
    string.push('a');
    string.push_str("QWER");
    string.len();
    let a = String::from("nihao");
    let b = String::from("nihao");
    let rs = a.eq(&b);
    let s: String = string::from("RUNOOB");
    let ch: char = s.chars().nth(2).unwrap();
    let sub: &str = &s[0..3];
}

&str 在一般情况下比 String 更实用,不需要把字符串当作可编辑数据对象的时候,可以有限选择使用 &str 作为数据类型

元组

fn main() {
    let tup = (500, 6.4, 1);
    let tup2: (i32, f64) = (233, 2.33);
    let (a, b) = tup2;
    println!("{} {} {} {} {}", tup.0, tup.1, tup.2, a, b);
}

元组中的存储允许数据类型不一样,元组可以帮助函数返回多个值

数组

不同于元组,数组中所有的数据类型必须一致,而且两者的使用方式不同,rust 中的数组不同于 python, 不是一个链表,不能灵活拓展,必须有固定长度,指定数组长度的时候不能使用变量,数组的长度必须在编译时就确定

fn main() {
    // 创建数组
    let a = [1, 2, 3, 4, 5];
    let b: [i64; 3] = [1, 2, 3];
    let c = [3; 5]; // <-> let c = [3, 3, 3, 3, 3];
    // 从数组中取出数字
    let first = c[0];
    let second = c[1];
    let third = c[2];
    // 改变数组中的数字需要数组是 mut
    let mut a = [1, 2, 3];
    a[0] = 2;
    // 获取数组长度
    println!("{}", c.len())
}

注释

rust 的注释方式和 c、java 等主流语言中相同

// 行

/* 
 * 行
 * 行
 * 行
 */

除了这些常规的注释之外,rust 还允许开发者编写说明文档注释,表明函数的用途,使用方法是使用 ///

/// add two number
/// - a + b
/// # For example:
/// ```
/// let x = ad(1, 2);
/// ```

fn ad(a: i32, b: i32) -> i32 {
    return a + b;
}

fn main() {
    println!("{}", ad(1, 3));
}

这样就对 ad 这个函数进行了说明

差不多该换一个文档写笔记了吧(?

函数

rust 中的函数声明规则为

fn 函数名(参数: 参数类型, ...) -> 返回值类型 {
    ...
}

为什么用 -> 这种符号?

下面是一个简单的例子

fn f(a: i32, b: i32) -> (i32, i32) {
    return (a + b, b - a);
}

rust 并不是不强制分号,而是分号是区分语句(Statement)和表达式(Expression)的标志

如果一行代码有分号,那么它就是一条语句,如果一行代码没有分号,那么它就是表达式

233 // 表达式
1 + 2 // 表达式
let a = 1 + 2; // 语句

没有返回值就无法成为一个表达式,赋值语句没有返回值,所以如果一条赋值语句没有分号,那它不会成为表达式,而是会被编译器报错,表达式这个概念在 rust 中非常重要

在函数中,可以直接用 return 返回值,也可以不用 return, 在最后一行使用一行表达式,返回表达式的值

fn f(a: i32, b: i32) -> (i32, i32) {
    (a + b, b - a)
}

上面这个函数和刚刚开始举的例子是一样的效果

函数表达式的概念是用 {} 包裹的包含语句以表达式结尾的语句块,不支持 return

let z = {
    let a = 233;
    let b = 2333;
    a + b
};

上面的 ab 都是临时变量,z 的值就是 233 + 2333

在大多数脚本语言中,函数也是可以被赋值的,rust 中也有类似的功能

fn f1() {
    println!("1");
}

fn f2() {
    println!("2");
}

fn main() {
    let mut fc: fn();
    fc = f1;
    fc(); // f1();
    fc = f2;
    fc(); // f2();
}

Lambda

闭包 居然和 Lambda、匿名函数 是同一个意思,用来快捷地传递函数,广泛应用于异步编程

rust 中 Lambda 表达式的写法是 |参数, ...| -> 返回值类型 { 函数体 }

let a = || 12;
let b = |x: i32| x + 1;
let c = |x: i32| -> i32 { x + 1};

以上三行都是正确的 Lambda 表达式,返回类型编译器是可以自动推断的,所以有时候可以省略不写,但还是推荐写最下面的那种,出错的概率会小一点

虽然 Lambda 支持自动判断类型,但是普通函数还是必须要指定类型的,公开的函数一定要有可见的声明

条件语句

if

语法和 go 类似,if 不用括号括起来(但是也可以用括号括起来

fn main() {
    if 1 == 2 {
        println!("wtf");
    }
    else if 2 == 3 {
        println!("wtf");
    }
    else {
        println!("so damn right");
    }
}

就是也和 go 一样,条件必须是 boolean 类型的,不能像 c 那样自由地用 1 和 0 来判断

行 if

也叫三元运算符,不同于 c 中的 a ? b : c

rust 中的三元运算符和 python 中的类似 if 条件 { true执行 } else { false执行 }

fn main() {
    let a = 3;
    let number = if a > 0 { 2 } else { 0 };
    println!("{}", number);
}

还支持 else if

let number = if a > 0 { 2 } else if a == 0 { 1 } else { 0 };
let score = 86;
let branch = if score > 90 {
    "great"
}
else if score > 80 {
    "good"
}
else if score > 70 {
    "normal"
}
else {
    "bad"
};

这不就是把 if 折过来吗(?

函数表达式的返回值类型必须相同

经过 go 的洗礼,我发现我好像有点看不习惯 }else 不同行了,希望之后能改回原来的观念

match

和 switch 语句差不多,所有语句都要列出

fn main() {
    let op = 3;
    match op {
        0 => {
            println!("{}", op);
        }
        2 | 3 | 4 => {
            println!("is 2 or 3 or 4");
        }
        _ => {
            println!("other");
        }
    }
    let y = 3;
    let x = match y {
        1 => 2,
        2 => 3,
        3 => 4,
        _ => 5,
    };
    println!("{}", x);
}

match 的语法特点不止这些,官网有更详细的说明,链接

循环

rust 中有三种循环 while、for、loop

while

和 C 的 while 类似,语法都是 while 循环条件 {...}

fn main() {
    let mut x = 0;
    while x < 5 {
        println!("{}", x);
        x += 1;
    }
}

for

现在的脚本语言,像 python 那样的,一般都会用 foreach 那样的循环,rust 和 python 一样,直接用 foreach 替代了传统的 for 循环,所以 rust 中没法使用像 C 语言中那种 for 循环了

fn main() {
    // 输出 1,2,3,4
    for i in 1..5 {
        println!("{}", i);
    }
    // 迭代器
    let l = [20, 10, -2, 30, 60];
    for i in l.iter() {
        println!("{}", i);
    }
    // 下标
    for i in 0..l.len() {
        println!("l[{}] is {}", i, l[i]);
    }
    // 啥都不输出
    for i in 4..-1 {
        println!("{}", i);
    }
}

go 是把 while 给删了,只用 for 这一点上 rust 和 go 是完全相反的改动

以免忘了 go 的语法,记一下 go:

for i := 0; i <= 10; i++ {
    ...
}
// while true {...}
for ;; {
    ...
}

loop

一直循环,while true {...} 的替代品,原来 while-true 写法并不优雅,loop 循环中必须要有 break语句,不然编译器是不会允许通过的,而 loop 也是三种循环中唯一一种支持函数表达式的循环

fn main() {
    let mut x = 1;
    loop {
        println!("{}", x);
        if x == 100 {
            break;
        }
        x += 1;
    }

    x = 0;
    let s = ['a', 'b', 'c'];
    let y = loop {
        if s[x] == 'b' {
            break x;
        }
        x += 1;
    };
    println!("{}", y);
}

这篇笔记暂时完结在这里吧,接下去的内容新开一篇笔记,总结一下,就是粗糙的过了一下基本语法:输入输出、赋值、变量类型、数据类型、注释、函数、判断、循环

接下来还有一堆 rust 的知识点需要学习

学这么多语言有很大概率会搞混啊,所以记一记还是很有必要的

2023/03/21
> CLICK TO back <