rust 快速入门

Created

2024-09-12 17:30:16

Updated

2024-10-27 22:43:08

1 入门例子

hello.rs
// fn : function
fn main() {
    // rust的缩进是4个空格
    // println! 是一个 rust macro 宏, 不是函数, 如果结尾没有! 则是函数
    println!("hello"); // 结尾 要有 ;
    /* let 定义变量及初始化 */
    // : i32 这种表示 变量x的类型
    let x: i32 =6;
    println!("{:?}", x);
    let y: i32;
    println!("{:?}", y); // 报错, 没有初始化会报错

    // 定义了变量,但是没有使用它,rust会警告你,_开头的 rust不会警告
    let _z = 5;

    let a: i32;
    // println!("{a}");  使用之前必须初始化,否则报错
    // 但是如果不使用的话,  是不会报错的

    // _ 表示忽略这个变量绑定
    let _ = 4;
    // println!("{}", _); 报错, _ 不是变量名
}

rustc 这个只适合编译简单的程序,大型程序 用cargo

# -o 输出的名字, 没有则默认文件名
rustc hello.rs -o main
Caution

将类型放到变量的后面的设计, 是考虑了rust想要类型自动推导.

2 immutable

fn main() {
    // 变量默认不可变
    let a: i32 = 5;
    // a=2;  会直接报错,提示不能修改 不可变的变量
    println!("{:p}", &a);

    // 申明可变变量
    let mut c: i32 = 5;
    c = 7;
    println!("{c}");
}

3 shadowing

fn main() {
    let a: i32 = 5;
    // a=2;  会直接报错,提示不能修改 不可变的变量
    println!("{:p}", &a);
    // shadowing  隐藏了上面的变量a
    let a: i32 = 5;
    // 与上面的地址不同, 已经是一个不同的变量了,只是用了相同的变量名
    // 一开始申明的a 被隐藏了
    println!("重新申明的a变量地址: {:p}", &a);

    let b = 5i32;
    {
        // 在这个作用域中, b 也确实shadowing 了外面的b
        let b = 8i32;
        println!("b 在作用域中的值: {b}"); // 是8
    }
    // 但是在退出块作用域后, b还是5
    println!("作用域外,b的值:{b}");

    // shadowing 可以使用完全不同的类型, 因为实际就是2个完全不同的变量
    let b = "abc";
    println!("{b}");
}

一个变量转换类型后,想要用原来的变量名, 就用shadowing方式使用同名变量即可

希望变量一开始可读写,之后想要弄成只读
fn main() {
    let mut a = vec![1, 2, 3];
    a.push(4);
    // 这以后就是只读的了. 可以称为冻结
    let a = a;
}
fn main() {
    // 不可变
    let a = vec![1, 2, 3];
    // 通过获取所有权, 这里又可以变成可变
    let mut a = a;
    a.push(4);
}

4 原生类型

primitive type

4.1 标量类型

scalar type

Tip

标量类型是指那些不可再分的、基本的、单个值的数据类型。 标量类型通常用于存储单一的数据项,如一个数字、一个字符或一个布尔值。 与标量类型相对的是复合数据类型,如数组、列表、对象和结构体,这些类型可以包含多个值

4.1.1 整型

use std::mem;
fn main() {
    // 有符号 i8 i16 i32 i64 i128 isize(根据系统架构决定)
    // 无符号 u8 u16 u32 u64 u128 usize(根据系统架构决定)
    let a=6;  // 不写类型,默认就是i32
    // 打印i32类型的大小
    println!("{}",mem::size_of::<i32>());
    // 打印变量占用的内存大小
    println!("{}",mem::size_of_val(&a));
    let mut inferred_type = 12; // 根据下一行的赋值推断为 i64 类型
    inferred_type = 4294967296i64;
    // 十进制 2_100  下划线只是为了增加可读性, 这里相当于美国的千位加,的意思
    let b:i32=2_100;
    println!("{}",b);
    // hex 16进制 0x 开头
    let c:i32=0xff;
    println!("{}",c);
    // 八进制 0o 开头
    let d:i32=0o12;
    println!("{}",d);
    // 二进制
    let e:i32=0b1111_0000;
    println!("{}",e);
    // byte 
    let f:u8=b'A';
    println!("{}",f);  
    // 可以使用类型后缀, 在值后边写上
    let g=5i16;
    println!("{}",g);
}
fn main() {
    println!("{}", i32::MAX); // i32最大值
    println!("{}", 2_u16.pow(4)); // 求指数幂
    println!("{}", (-4_i32).abs()); // 求绝对值
    println!("{}", 0b0011_1101_u8.count_ones()); // 位计数(多少个1)
    // 可以使用这种方式来调用方法
    println!("{}", i32::abs(-4));
    println!("{}", i8::count_zeros(0b0011_1101));

    // Checked_ 操作返回一个结果的 Option 值:如果运算结果可以被结果类型正确表示就返
    // 回 Some(v),否则返回 None
    assert_eq!((10_u8).checked_add(20), Some(30));
    assert_eq!((100_u8).checked_add(200), None);

    // wrapping_ 的结果是对 2的8次方 256的取模得到的余数
    assert_eq!(16_u8.wrapping_mul(15), 240);
    assert_eq!(18_u8.wrapping_mul(15), 14);

    // 有符号数的操作可能会回环成负数。
    // 0111 1111 + 1  =>1000 0000 补码的 -128
    assert_eq!(127_i8.wrapping_add(1), -128);
    // 0111 1111 + 1+1  => 补码 1000 0001 => 反码:1000 0000 ==> 1111 1111 原码
    assert_eq!(127_i8.wrapping_add(2), -127);

    // 在移位操作中,移动的位数会回环到该类型的位数之内
    // 因此对 8位的数字移动 9位等于移动 1位
    // 0000 0101 相当于左移 1位=> 0000 1010
    assert_eq!(5_i8.wrapping_shl(9), 10);

    // Saturating_ 操作会返回最接近正确结果的表示,结果被“截断”到这个类型能表示的最大或最小值
    assert_eq!(127_i8.saturating_add(1), 127);

    // Overflowing_ 操作返回一个 tuple (result, overflowed),其中result 是回环版本的方法
    // 返回的结果,而 overflowed 是一个指示是否发生溢出的 bool 值:
    assert_eq!(255_u8.overflowing_sub(2), (253, false));
    assert_eq!(255_u8.overflowing_add(2), (1, true));
    // 只有当位移距离大于等 于类型的位宽度时 overflowed 才为 true
    // 实际的移位距离等于要求的距离对位宽度取余后的结果
    // 0000 0101 相当于左移1位 (9%8)=> 0000 1010
    assert_eq!(5_u8.overflowing_shl(9), (10, true));
    // 0000 0101 左移6位=> 1 0100 0000: 64
    assert_eq!(5_u8.overflowing_shl(6), (64, false));
    // is_numeric 数字,字符0-9 , 还有这以外,也有是数字的
    println!("{}", '①'.is_numeric());  //true
}

4.1.2 浮点数

fn main() {
    let x = 2.0; // 不写类型,默认就是f64
    let y: f32 = 3.0; // f32
    // 科学计数法
    let a = 1e6; // f64类型
    let b = 7.6e-4; // f64类型
    println!("a is {}", a);
    println!("a is {}", b);
}
fn main() {
    // 平方根
    println!("{}", 8f32.sqrt());
    println!("{}", 8.2f32.floor()); // 8
    println!("{}", 8.9f32.floor()); // 8
    println!("{}", (-8.9f32).floor()); // -9
    // 方法调用的优先级高于前缀运算符,因此对负数调用方法时确保要用括号括起来
    println!("{}", -8.9f32.floor()); // -8
    println!("{}", 8.2f32.round()); // 8
    println!("{}", 8.5f32.round()); // 9
}
use std::f32::consts;
fn main() {
    // rust 提供了一些常量, 比如 PI
    println!("{}", consts::PI);
}

4.1.3 bool

fn main() {
    let t = true;
    println!("{t}");
    // 同样必须初始化,否则报错
    let f: bool = false;
    println!("{f}"); // false
    // bool 可以转 整型
    let d = false as i8;
    println!("{d}"); // 0
    // 整型不能转 bool
    //    let c = 1i8 as bool;
}

4.1.4 char

Tip

char 设计的目的是用来存储任何一个unicode 字符, 所以它的大小是4个字节

use std::mem;

fn main() {
    let c = 'z'; // 这个是字符, 和前面 b'z' 是整型不同哦
    println!("{}", mem::size_of_val(&c)); // 4
    let z: char = 'ℤ';
    println!("{c}-{z}");
    // rust 中的char 是 4个字节,Unicode, 可以表示表情
    let heart_eyed_cat = '😻';
    println!("{heart_eyed_cat}");

    let a = 'a';
    println!("{}", a as u8); // 97
    // 标准库提供了函数 std::char::from_u32 接受
    // 任何 u32 值,并返回 Option<char>:
        // 如果 u32 的值不是合法的 Unicode 码点,from_u32 会返回 None;
        // 否则,它会返回 Some(c),c 就是作为转换结果的 char
    let hex_char = char::from_u32(97);
    println!("{:?}", hex_char);

    let tr = '\n';  // 换行
    let good = '\u{597D}'; // 好 unicode
    println!("world {} {}", tr, good);

    let a = 'c';
    println!("{}", a.is_alphabetic()); // 是否为字母
    let b = 'ß'; // 这个是非 ASCII 字符, 很多也被认为是字母
    println!("{}", b.is_alphabetic()); // true
}

4.2 复合类型

compound type

4.2.1 tuple

fn main() {
    // 元素可以是多个,且可以是不同的类型, 确定后, 
    // 该元组tup 的类型是(i32, f64, u8),它的大小就固定了
    let tup: (i32, f64, u8) = (100, 3.14, 2);
    println!("{},{},{}", tup.0, tup.1, tup.2);
    let (x, y, z) = tup;
    println!("{},{},{}", x, y, z);
}
use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}
fn main() {
    let a = (1,);
    println!("{}", a.0);
    let b = (1);
    print_type_of(&b);
    // 单元类型,虽然是个元组, 一般会认为它不是复合类型
    // 函数那里会讲到
    let c = ();
}

4.2.2 数组

Tip
  • 元素类型必须一致
  • 长度是固定的,运行时不能修改, 长度是类型的一部分
  • 表示方法: [T;n] T表示泛型,后面会说, 这里就理解为数组元素的类型, n 表示元素个数
  • 数组是在栈上分配的单个块的内存
  • 常用于开辟一个固定大小的Buffer作为缓冲区,比如接收IO输入输出等
  • 等学了所有权之后,我们知道把数组作为参数传递给函数,是直接复制一份给函数参数,而不是我们某些其他语言里传递指针
fn main() {
    let x = [1, 2, 3, 4];  // 默认类型是[i32; 4]
    println!("{}", x[1]);
    // [i32;5] 表示每个元素是i32,一共有5个元素
    let x: [i32; 5] = [1, 2, 3, 4, 5];
    println!("{}", x.len());
    // 相当于 let x=[3,3,3,3,3];
    let x = [3; 5];
    // 下标读取
    println!("{}", x[2]);

    // 空数组
    let x: [i8; 0] = [];
    println!("{}", std::mem::size_of_val(&x)); // 0
    // is_empty() 源码就是 判断 self.len() == 0
    println!("{}", x.is_empty()); // true

    // 数组长度是类型的一部分, 只有元素类型和元素个数一样的才是同一种数组类型
    let mut a=[1,2,3,4];
    let b=[2,3,4,5];
    a=b; // ok

    unsafe {
        // 可以去看unsafe 章节
        // 数组的裸指针,类型是数组元素类型 这里是i32
        let ptr = a.as_ptr();
        println!("{:p}-{:p}", a.as_ptr(), &a);
        println!("{}-{}", *ptr, *ptr.wrapping_add(1))
    }
}
Caution
  • 数组不鼓励用索引进行操作, 因为我们知道每次索引操作都会进行边界检查,看不是是溢出了,效率略有影响
  • 推荐后面说到的迭代器
多维数组
fn main() {
    let x: [[i32; 2]; 5] = [[1, 11], [2, 22], [3, 33], [4, 44], [5, 55]];
    for i in &x {
        println!("x {:?}", i);
    }
}

4.2.3 切片

Note
  • 医学上切片的意思是将组织样本切成薄片以便于在显微镜下观察,可以说切片是原物品的一部分
  • 编程上切片的意思: 原数据一段连续部分的引用,不难推断出它的数据结构是一个原数据的地址和引用的长度
use std::mem;
fn main() {
    let mut x = [1, 2, 3];
    println!("{}", mem::size_of_val(&x));  // 12
    println!("{:p}", x.as_ptr());
    // 切片是对一块连续内存数据的引用
    // 由2个部分组成, 一个是data指向数据,一个len 表示长度
    // 这种比一般指针要多出一些信息的叫 胖指针
    let y = &mut x[1..3];
    println!("{:p}", y.as_ptr());
    println!("{}", y.len());
    println!("{}", mem::size_of_val(&y));  // 16

    let x = [1, 2, 3, 4, 5];
    let (a, b) = x.split_at(2);
    println!("{:?}", a); // [1,2]
    println!("{:?}", b); // [3,4,5]
}
// &[T] 切片的表示方式
fn analyze_slice(slice: &[i32]) {
    println!("First element of the slice: {}", slice[0]);
    println!("The slice has {} elements", slice.len());
}
fn main() {
    let x: [i32; 5] = [2, 1, 4, 3, 5];
    let y = &x[1..3];
    analyze_slice(&y);

    let mut chaos = [3, 5, 4, 1, 2];
    //这里会隐式的把数组的引用转换为切片
    chaos.sort();
    println!("{:?}", chaos);
    chaos.reverse();
    println!("{:?}", chaos);

    let x: [i32; 5] = [2, 1, 4, 3, 5];
    // 也是隐式地转换为切片类型,才会可以for 循环
    for i in &x {
        println!("x {}", i);
    }
}

5 类型转换

Important
  • Rust 不提供原生类型 之间的隐式类型转换(coercion),但可以使用 as 关键字进行显式类型转换(casting)
  • rust 是静态强类型, 需要显示表明你要转换的类型

5.1 as

fn main() {
    let a: i8 = 9;
    // 需要加上 as, 否则报错
    let b: i32 = a as i32;
    println!("{}",b);
}

5.2 transmute

Caution

不安全的转换, 需要 unsafe


use std::mem;
use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}
fn main() {
    // 这个元素的类型会根据你后面的transmute 里的src-type 来推断
    // 之前在数组那里我们了解到  let x = [1, 2, 3, 4]; => 默认类型是[i32; 4]
    let raw_bytes = [1, 2, 3, 4];
    print_type_of(&raw_bytes);
    // transmute::<src-type, dst-type>(src:src-type)
    let num = unsafe { std::mem::transmute::<[u8; 4], u32>(raw_bytes) };
    // 100 00000011 00000010 00000001
    println!("{:b}", num);
    println!("{}", num); // 67305985
    let num = 67305985u32;
    let r = unsafe { std::mem::transmute::<u32, [u8; 4]>(num) };
    println!("{:?}", r);  // [1, 2, 3, 4]
}

指针类型转换

fn main() {
    let a = 67109377;
    // 将变量a的地址 转换为一个指针 ,指针类型是i32,这个就是a原来的内存读取方式
    // 然后再将这个指针 进行类型转换, 转换为 数组的方式来读取 a 所在的那块内存
    let r1 = &a as *const i32 as *mut [i8; 4];
    unsafe {
        println!("{:?}", (*r1));
        (*r1)[0] = 5;
        println!("{:?}", (*r1));
        println!("{}", a);
    }
}
Warning

下面这个操作, 没有得到你想要的结果, 元组可能因为内存对齐的原因, a的前2个字节组成元组的第二个元素, 看看就好.

fn main() {
    let a = 67244033i32;
    //用 元组的形式区读取原本 a的那块内存
    let d = unsafe { std::mem::transmute::<&i32, &(i8, i16, i8)>(&a) };
    println!("{:?}", d);
}
可变转换,然后可如此这般修改原来的数据
fn main() {
    let mut a = 67244033i32;
    //用 元组的形式区读取原本 a的那块内存
    let d = unsafe { std::mem::transmute::<&mut i32, &mut [i8; 4]>(&mut a) };
    println!("{:?}", d); // [1, 16, 2, 4]
    d[0] = 3;
    println!("{:?}", d); // [3, 16, 2, 4]
    println!("{}", a); //67244035
}

5.3 使用trait

直接看 类型转换相关trait

6 类型别名

// 定义一个类型别名
// 这使得代码更加简洁,更具可读性, 因为 Coordinates 比 (f64, f64) 更能表达其用途
type Coordinates = (f64, f64);

// 使用类型别名
fn calculate_distance(point1: Coordinates, point2: Coordinates) -> f64 {
    let (x1, y1) = point1;
    let (x2, y2) = point2;
    ((x2 - x1).powi(2) + (y2 - y1).powi(2)).sqrt()
}

fn main() {
    let point_a: Coordinates = (0.0, 0.0);
    let point_b: Coordinates = (3.0, 4.0);
    let distance = calculate_distance(point_a, point_b);
    println!("距离: {}", distance);
}
fn main() {
    // 类型别名
    // 让类型含义更加的有意义
    type Kilometers = i32;

    let x: i32 = 5;
    let y: Kilometers = 5;
    // 实际还是是i32 所以可以 +
    println!("x + y = {}", x + y);
}
Caution

下面几个例子,有些知识点在后续介绍

减少重复代码, 让代码更简洁

    type Thunk = Box<dyn Fn() + Send + 'static>;

    let f: Thunk = Box::new(|| println!("hi"));

    fn takes_long_type(f: Thunk) {
        // --snip--
    }

    fn returns_long_type() -> Thunk {
        // --snip--
    }

我们可以看到 Result<usize, Error> 类似这样的很多, 都要写Error

use std::fmt;
use std::io::Error;

pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize, Error>;
    fn flush(&mut self) -> Result<(), Error>;

    fn write_all(&mut self, buf: &[u8]) -> Result<(), Error>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Error>;
}
fn main() {}

使用别名后的情况

use std::fmt;
// std::io 中有这样一个别名
// 标准库中类似这样的别名设计有还多
// type Result<T> = std::result::Result<T, std::io::Error>;

// 我们本地这样设计一个别名, 就不用每次 都写上Error
type Result<T> = std::io::Result<T>;
pub trait Write {
    fn write(&mut self, buf: &[u8]) -> Result<usize>;
    fn flush(&mut self) -> Result<()>;

    fn write_all(&mut self, buf: &[u8]) -> Result<()>;
    fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<()>;
}
fn main() {}

7 静态变量

Important
  • 使用 static ,一般在所有其他作用域之外声明, 作为全局变量
不可变静态变量
struct Animal {
    age: i32,
}
impl Drop for Animal {
    fn drop(&mut self) {
        println!("animal 离开作用域了...{}", self.age);
    }
}
fn main() {
    let a = 22;
    {
        // 申明时必须初始化,必须指定类型
        static b: Animal = Animal { age: 11 };
    }  // 不会调用 drop , 虽然无法访问b, 但是它不会被释放内存
    let c = 11;
    println!("{}", a);
    {
        let d = Animal { age: 100 };
    } // 这个会调用drop
    println!("{}", c);
}
可变的静态变量,修改需要unsafe
static mut COUNTER: u32 = 0;
fn add_to_count(inc: u32) {
    unsafe {
        COUNTER += inc;
    }
}

fn main() {
    add_to_count(3);

    println!("COUNTER: {}", unsafe { COUNTER });
}
static mut COUNTER: u32 = 22;
const COUNTER2: i32 = 11;
static mut S1: &str = "hello";
const S2: &str = "world";

fn main() {
    println!("{:p}", &COUNTER2);
    unsafe {
        println!("{:p}", &COUNTER);
    }
    unsafe {
        let s11 = S1.as_ptr(); // *const u8
        println!("S1 指向的字符所在内存地址: {:p}", s11);
        println!("{}", *s11); // 第一个h 字符
        S1 = "world";
        let s11 = S1.as_ptr();
        // let s11 = S1.as_ptr() as *mut u8;
        println!("S1 新指向的字符所在内存地址: {:p}", s11);
        println!("{}", *s11);
        let s22 = S2.as_ptr();
        println!("S2 指向的字符所在内存地址:{:p}", s22);
    }
}

如果需要第一次使用才去初始化的静态变量 ,可以使用库once_cell,lazy_static

8 常量

Important
  • 常量是在编译期就进行求值的. 所以常量 = 的右边是可以使用表达式的
  • 常量在编译过程中会被内联优化,这个表示每个使用常量的地方都会直接替换为常量的值(如果没记错的话, 可类比汇编中的立即数..),不会占用内存
  • 因此修改常量的值本身就没有意思
const A: u32 = 60 * 60 * 3;

fn main() {
    // 一般用大写并且下划线来申明常量
    // 不可修改, 不可shadowing
    // 不可以使用mut,永远不可变
    // 可以在任何作用域申明
    const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
    println!("{}", THREE_HOURS_IN_SECONDS);
}
const fn
// 编译期执行
const fn g() -> i32 {
    // 返回值视为常量
    10 * 20
}
const A: i32 = g();
fn main() {
    println!("{}", A);
}

9 函数

Tip
  • 语句: 执行动作, 没有返回值
  • 表达式: 是有一个值的
  • 函数体是由一系列语句和一个结尾(可以是语句或者表达式)组成
  • unit type 单元类型 () 表示一种类型. 表示没有什么特殊的价值, 它的值就是它本身 也是()

9.1 返回值

fn five() -> i32 {
    // 表达式 ,值为5
    5
}

fn main() {
    let x = five();

    println!("The value of x is: {x}");
}

添加;号试试, 会直接报错提示 期望i32, 而实际发现是the unit type ()

// -> i32 , 表示返回值类型
fn five() -> i32 {
    5;
}

我们将上面的返回值类型改成 () , 这样就不会报错了

fn five() -> () {
    5;
}

实际上就是不用写返回值 的类型

// 没有指明返回值的, 那么返回值类型就是 unit type :()
fn five(){
    5;
}
fn main() {
    // 单元值判断
    if five() == () {
        // ok的
        println!("ok");
    }
}
fn t() {
    // 这里返回了5, 要求的是(),所以会提示错误
    5
}

9.2 函数作为参数

fn calc(m: fn(u32, u32) -> u32, a: u32, b: u32) {
    println!("add result: {}", m(a, b));
}
fn add(a: u32, b: u32) -> u32 {
    a + b
}
fn main() {
    calc(add, 1, 2);
}

9.3 函数作为返回值


// 传入字符串:函数名,获取函数
fn calc(m: &str) -> fn(u32, u32) -> u32 {
    match m {
        "add" => add,
        _ => unimplemented!(),
    }
}
fn add(a: u32, b: u32) -> u32 {
    a + b
}

fn main() {
    let f = calc("add");
    let r = f(1, 2);
    println!("{}", r);
}

9.4 函数内部创建函数

fn main() {
    // 函数内部可以定义函数
    fn add(t1: i32, t2: i32) -> i32 {
        t1 + t2
    }
    let r = add(1, 2);
    print!("{}", r);
}

9.5 函数的类型

fn add1(t: (i32, i32)) -> i32 {
    t.0 + t.1
}
// 实际看来 与add1 参数一样
fn add2((t1, t2): (i32, i32)) -> i32 {
    t1 + t2
}
fn main() {
    let a = 1;
    let b = 2;
    let c = (1, 2);
    println!("{}", add1(c));
    println!("{}", add2((a, b)));

    // f1的类型是 fn (t: (i32, i32)) -> i32
    let f1 = add1;
    // 报错了 expected fn item, found a different fn item
    // 不同的类型, 所以报错了.
    // f1 = add2;

    // 正确的方式是 定义变量时将 他的类型转换为 fn item 通用的fn 类型
    let f1 = add1 as fn((i32, i32)) -> i32;
    println!("{}", f1((1, 2)));

    // 这样定义也可以
    let mut f1: fn((i32, i32)) -> i32 = add1;
    f1 = add2; // 不会报错了
    println!("{}", f1((a, b)));
}

9.6 发散函数

diverging functions

返回值是 ! ,被称为 The Never Type

我们知道前面提到的函数其实都是有返回值, 即使是没有返回值的函数, 也会返回一个 () 单元类型的值:()

那么一个 永远不会返回的函数它有返回值吗? 还有 panic 这种函数呢?

fn main() {
    let a = bar();
    a=2;// 虽然不会执行到这里, 但是编译器不会报错
    println!("{}", a);
}
// 返回 ! never type
fn bar() -> ! {
    // 永远不会返回,但是有rust 设计它有返回值, 类型是!
    loop {
        print!("and ever ");
    }
}

fn foo() ->!{
    panic!("error");
}

9.6.1 为什么需要发散类型

下面我们都知道会报错

fn main() {
    let guess = "42";
    let guess = match guess.trim().parse() {
        Ok(_) => 5,
        Err(_) => "hello", // 报错了
    };
}

下面这个没有报错

fn main() {
    let r = bar();
}
fn bar() -> ! {
    let guess = "42";
    loop {
        let guess = match guess.trim().parse::<i32>() {
            Ok(_) => 5,
            // ! 发散类型可以被转换为任何类型
            Err(_) => continue, 
        };
    }
}
fn main() {
    let r = bar(0);
    println!("{:?}", r);
}
fn bar(x: i32) -> i32 {
    if x == 0 {
        // 这里panic 了, 但是函数没有报错
        panic!("no 0");
    } else {
        x * 2
    }
}

10 控制流程

10.1 if

fn main() {
    let number = 3;
    // 报错的, 需要bool类型
    if number {
        println!("number was three");
    }

    let condition = true;
    // 当成 三元 运算, 实际上这里的表达更加清晰,一看就明白
    // 而三元运算你是需要稍微学习或者说记一下
    // 这也是有些语言它不去支持三元运算的原因
    let number = if condition { 5 } else { 6 };

    println!("The value of number is: {number}");
    let number2 = if condition {
        5;
        7  // 还是看的返回值
    } else {
        6
    };

    println!("The value of number is: {number2}");

    // 报错
    let number3 = if condition { 5 } else { "six" };
}

10.2 loop

fn main() {
    let mut i = 0;
    let ret = loop {
        i += 1;
        if i > 10 {
            break i; // 返回值, 就是loop的返回值
        }
    };
    println!("{}", ret)

    let y: i32;
    // 编译器是能够判断出 后面的打印时, y肯定已经赋值了.
    let x = loop {
        y = 1;
        break;
    };
    println!("{:?}", x);
    println!("{:?}", y);
}

跳出多层 loop

#![allow(unreachable_code, unused_labels)]
fn main() {
    'outer: loop {
        println!("Entered the outer loop");

        'inner: loop {
            println!("Entered the inner loop");
            // This would break only the inner loop
            //break;
            // This breaks the outer loop
            break 'outer;
        }
        println!("This point will never be reached");
    }
    println!("Exited the outer loop");
}

10.3 for

Caution
  • for 循环是利用迭代器对数据进行遍历的,参考后面迭代器章节
  • 标签 label的使用方式与loop一样
fn main() {
    // label 前面有个 ' 单引号
    'outer: for i in 0..5 {
        println!("Outer loop value: {}", i);

        for j in 0..5 {
            println!("Inner loop value: {}", j);

            if i == 2 && j == 3 {
                break 'outer;
            }
        }
    }

    let a = [10, 20, 30, 40, 50];

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

    // (1..4)  1 2 3
    // 会提示不需要 用 () 将1..4 阔气来
    for number in (1..4) {
        println!("{number}!");
    }
    // 不包含4
    for number in 1..4 {
        println!("{number}!");
    }
    // =4 包含4
    for number in 1..=4 {
        println!("{number}");
    }

    for number in 1.. {
        if number == 10 {
            break;
        }
        println!("{}", number);
    }

    use std::ops::Range;
    let a = Range { start: 1, end: 10 };
    for j in a {
        println!("{}", j);
    }
    // x 实际上是一个RangeFrom<i32> 类型
    // 0..5 这种实际就是一个语法糖
    let x = (0..);
    // x 实际为Range类型
    let x = (0..5);
}

数组来测试 range


#[cfg(test)] //配置测试模块
mod abc {
    #[test] //单元测试用例
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
    #[test]
    fn arr_range() {
        let arr = [0, 1, 2, 3, 4];
        assert_eq!(arr[..], [0, 1, 2, 3, 4]);
        assert_eq!(arr[..3], [0, 1, 2]);
        assert_eq!(arr[..=3], [0, 1, 2, 3]);
        assert_eq!(arr[1..], [1, 2, 3, 4]);
        assert_eq!(arr[1..3], [1, 2]); // This is a `Range`
        assert_eq!(arr[1..=3], [1, 2, 3]);
        assert_eq!((3..5), std::ops::Range { start: 3, end: 5 });
    }
}

10.4 while

fn main() {
    let z: i32;
    while true {
        z = 1;
        break;
    }
    // 打印这个的时候, 编译器无法直到 z到底是不是赋值了,
    // 因为他认为while 能否进去是要判断的,
    // 当然我们一看就知道是true 能进去,但是编译器不行
    // 相比while loop 肯定是能进去的.
    println!("{}", z); //报错了
}

11 测试

#[cfg(test)]  //配置测试模块
mod tests {
    #[test]  //单元测试用例
    fn it_works() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
    // 再来一个测试用例
    #[test]
    fn it_works2() {
        let result = 2 + 2;
        assert_eq!(result, 4);
    }
}
cargo test

12 查看汇编

rustc --emit=asm main.rs
rustc -C debuginfo=1 -o output.s --emit asm -Cllvm-args=--x86-asm-syntax=intel --crate-type rlib  --edition 2021 src/main.rs

rust-lang playground里可以选择asm 查看
godbolt

13 打印信息

13.1 打印变量大小

fn main() {
    let a = 6;
    println!("{}", std::mem::size_of::<i32>());
    // 打印变量占用的内存大小
    println!("{}", std::mem::size_of_val(&a));
}

13.2 打印变量类型名

use std::any::type_name;

fn print_type_of<T>(_: &T) {
    println!("{}", type_name::<T>());
}

fn main() {
    let x = 42;
    print_type_of(&x); // 输出: i32

    let y = 3.14;
    print_type_of(&y); // 输出: f64

    let z = "hello";
    print_type_of(&z); // 输出: &str
}

13.3 常用打印

  • format!: 格式化文本到 String
  • print!: 同format! 只不过输出到 (io::stdout).
  • println!: 同print! 但是换行
  • eprint!: 同 print! 只不过输出到 (io::stderr).
  • eprintln!: 同eprint! 但是换行
fn main() {
    println!("{} days", 31);
    // 多个可以使用 0 1 指定
    println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
    let x = "Alice";
    let y = "Bob";
    println!("{x}, this is {y}. {y}, this is {x}");
    println!("{a}, this is {b}. {b}, this is {a}", a = "Alice", b = "Bob");
    println!("Base 10:               {}", 69420); // 69420
    println!("Base 2 (binary):       {:b}", 69420); // 10000111100101100
    println!("Base 8 (octal):        {:o}", 69420); //八进制: 207454
    println!("Base 16 (hexadecimal): 0x{:x}", 69420); //16进制: 10f2c
    println!("Base 16 (hexadecimal): 0x{:X}", 69420); //大写16进制: 10F2C
    println!("科学计数法: {:e}", 10000i32); // 1e4
    println!("科学计数法 大写: {:E}", 10000i32); // 1E4
    println!("带换行和缩进的debug打印: {:#?}", [1, 2, 3]);
    println!("debug打印: {:?}", [1, 2, 3]);
    // > 表示往右对齐,那么就是左边填充
    println!("|{number:>5}|", number = 1); // 没写表示用空格填充, 4个空格 和一个1
    println!("{number:0>5}", number = 1); // 用0填充 :   00001
    println!("{number:0<5}", number = 1); // 用0填充 :   10000
    // 注意 需要 $
    println!("{number:0>width$}", number = 1, width = 5);


    let number: f64 = 1.0;
    let width: usize = 5;
    println!("{number:>width$}");

    // 浮点数 四舍五入
    println!("Dog: {:.3} ", 1.12385); // 1.124
}

13.4 Debug

Tip
  • 所有实现了fmt::Debug这个trait(暂时不用管这个概念)的类型,才能使用println!("{:?}"){:?}或{:#?}这种打印方式
  • 可自动推导,使用 #[derive(Debug)]
struct Cat(i32);

#[derive(Debug)] // 添加这个就自动实现了Debug trait
struct Dog(i32);
fn main() {
    println!("Cat: {:?} ", Cat(5)); // 报错了
    println!("Dog: {:?} ", Dog(5)); // ok
    // 使用这种 {0:?}  {dog:?}
    println!("Dog: {0:?} {dog:?}", Dog(5), dog = Dog(8));
}

13.5 Display

Tip
  • 所有实现了fmt::Display这个trait(暂时不用管这个概念)的类型,才能使用println!("{}"){}这种打印方式
  • 必须手动实现
use std::fmt;
struct Point2D {
    x: f64,
    y: f64,
}

// implement `Display` for `Point2D`.
impl fmt::Display for Point2D {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // 这里的写法类似 println! ,根据需要自定义输出信息
        write!(f, "x: {}, y: {}", self.x, self.y)
    }
}
fn main() {
    let p = Point2D { x: 3.3, y: 7.2 };
    println!("p: {}", p);
    // 实现了Display, 就能.to_string() 获取字符串 String
    println!("{}", p.to_string());
}

14 main()

14.1 返回值

Important

rust main 入口函数返回值必须实现了Termination trait, trait后面章节讲, 先理解为接口.

std::process::Termination
pub trait Termination {
    // Required method
    fn report(self) -> ExitCode;
}
// 返回值是 (), () 是实现了Termination trait
fn main(){
    // 编译运行后, echo $? 结果是0
}
fn main() -> ! {
    // 编译运行后, echo $? 结果是1
    std::process::exit(1);
}
fn main() -> impl std::process::Termination {
    std::process::ExitCode::SUCCESS
}
// 或者返回类型是 Result<T, E> where T: Termination, E: Debug 

其他实现了Termination的 : Infallible.

从外部导入作为main方法
mod foo {
    pub fn bar() {
        println!("Hello, world! rust");
    }
}
// 从外部crate 或者本地 import 为 main 方法
use foo::bar as main;
// 报错, 这里会提示已经定义了main函数
// fn main() {}

14.2 命令行参数

fn main() {
    // ./main ac -ver 1.2
    let args_struct = std::env::args();
    println!("{:?}", args_struct);
    let args: Vec<_> = args_struct.collect();
    println!("程序名: {}", &args[0]);
    println!("命令行参数: {:?}", &args[1..]); // ["ac", "-ver", "1.2"]

    let first_arg = match args.get(1) {
        Some(arg) => arg,
        None => {
            println!("没有提供足够的参数");
            return;
        }
    };

    let number = match first_arg.parse::<i32>() {
        Ok(num) => num,
        Err(_) => {
            println!("无法将第一个参数解析为整数");
            return;
        }
    };

    println!("第一个参数作为整数是: {}", number);
}

14.3 环境变量

fn main() {
    // 尝试获取环境变量,如果不存在则返回错误
    match std::env::var("HOME") {
        Ok(value) => println!("环境变量 HOME 的值是: {}", value),
        Err(e) => println!("无法获取环境变量 HOME: {}", e),
    }

    // 获取所有环境变量
    for (key, value) in std::env::vars() {
        println!("{}: {}", key, value);
    }
}

15 属性

15.1 dead_code

Tip
  • 编译器 默认会对 dead_code(死代码, 无效代码) 进行检查, 这会对未使用的函数产生警告. 可以用一个属性来禁用这个检查项
// 代码头部使用
// #! 开头的 作用于整个crate
#![allow(dead_code)]
fn used_function() {}

// # 开头的 用于模块或项, 这里是只作用于该函数
// `#[allow(dead_code)]` 属性可以禁用 `dead_code` 检查
#[allow(dead_code)]
fn unused_function() {}

fn noisy_unused_function() {}

fn main() {
    used_function();
}
Back to top