NipGeihou's blog NipGeihou's blog
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档

NipGeihou

我见青山多妩媚,料青山见我应如是
  • Java

    • 开发规范
    • 进阶笔记
    • 微服务
    • 快速开始
    • 设计模式
  • 其他

    • Golang
    • Python
    • Drat
  • Redis
  • MongoDB
  • 数据结构与算法
  • 计算机网络
  • 应用

    • Grafana
    • Prometheus
  • 容器与编排

    • KubeSphere
    • Kubernetes
    • Docker Compose
    • Docker
  • 组网

    • TailScale
    • WireGuard
  • 密码生成器
  • 英文单词生成器
🍳烹饪
🧑‍💻关于
  • 分类
  • 标签
  • 归档
  • 教程

    • 参考资料
    • 入门
    • 语法
      • 变量
      • 常量
      • 数据类型 - 标量类型
        • 整数
        • 浮点数
        • 布尔
        • 字符
      • 数据类型 - 复合类型
        • 元组 - Tuple
        • 静态数组
        • 动态数组 - Vector
        • 字符串 - String
        • 切片
      • 函数
        • 结构
        • 表达式
        • 返回值
        • 注释
      • 控制流
        • if
        • loop
        • while
        • for
        • 遍历数组
        • 数值遍历
      • 所有权
        • Stack 和 Heap
        • 数据存储
        • 数据访问
        • 所有权存在的原因
        • 所有权规则
        • 变量作用域
        • 内存和分配
        • 变量和数据交互的方式
        • 移动 - 复杂类型(类浅拷贝)
        • 复制 - 标量类型
        • 克隆(深拷贝)
        • 函数
        • 传递
        • 返回值
        • 引用与借用
        • 字符串切片
      • 结构体 - struct
        • 含义
        • 定义
        • 实例化
        • 读写
        • 更新(实例局部克隆)
        • Tuple struct
        • Unit-Like Struct(空结构体)
        • 所有权
        • 打印struct
        • 实例方法
        • 定义
        • 调用
        • 参数
        • 关联函数(静态方法)
        • 定义
        • 调用
      • 枚举
        • 定义
        • 调用
        • 定义方法
        • Option枚举
    • 常见库(Crate)
  • Rust
  • 教程
NipGeihou
2025-03-27
目录

语法

# 变量

// 不可变变量
let foo = 1;
// 可变变量:mut关键字为可变变量,没有则为不可变变量
let mut guess = String::new(); // :: 相当于调用静态方法

// 隐藏
foo = 2; // 会报错
let foo = 2; // 不报错,相当于重新声明一个foo变量,并将之前的隐藏
let foo = "Two"; // 新声明的变量可以是其他类型。

// 指定类型,如不指定类型则使用类型推断,rust可在编译时根据下文推到。
let x: u32 = 1;

# 常量

const MAX_POINTS = 100_0000;
  • 可声明在任何作用域
  • 名字命名规范全大写,下划线分割单词
  • 数值可使用下划线分割增加可读性

# 数据类型 - 标量类型

Note

存储在 Stack

# 整数

类型长度

Length Signed Unsigned
8-bit i8 u8
16-bit i16 u16
32-bit i32 u32
64-bit i64 u64
128-bit i128 u128
arch isize usize
  • isize 、 usize 的位数由程序运行的计算机的架构所决定,如 64 位计算机则为 64 位
  • 默认类型为 i32

定义(字面值)

Number Literals Example
Decimal 98_222
Hex 0xff
Octal 0o77
Binary 0b1111_0000
Byte(u8 only) b'A'
  • 除了 Byte 类型(只能是 u8)以外,其他数值字面值可使用类型后缀,如 57u8

# 浮点数

  • f32,32 位,单精度
  • f64,64,双精度,默认类型

数值运算

let sum = 5 + 10;
let difference = 95.5 - 4.3;
let product = 4 * 30;
let guotient = 56.7 / 32.2;
let reminder = 54 % 5;

# 布尔

let t = true;
let f: bool = false;

# 字符

let x = 'z';
let y: char = "N";

# 数据类型 - 复合类型

存储在 Heap 上

# 元组 - Tuple

// 定义一个元素依次为i32、f64、u8的元组
let tup: (i32, f64, u8) = (500, 6.4 ,1);

// 读取值:解构(与ES的解构赋值类似)
let (x, y, z) = tup;
println!("{}, {}, {}", x, y, z);

// 读取值:索引读取
println!("{}, {}, {}", tup.0, tup.1, tup.2);

  • 长度固定,一旦声明就无法改变
  • 元素类型不必相等

# 静态数组

let months = [
	"January",
	"……",
	"Decmber",
];

// 定义i32类型长度5的数组
let a: [i32;5] = [1,2,3,4,5];

// 另一种初始化方式:指定数组初始化值
let b = [3; 5];
// 相当于
let b = [3,3,3,3,3];


// 读取
let first = months[0];



  • 数组声明后,长度不可变

# 动态数组 - Vector

todo

# 字符串 - String

  • 字符串字面值:
    • 程序里手写的那些字符串值。它们是不可变的
    • 在编译时已知道内容,其文本直接被硬编码到最终的可执行文件里
    • 速度快、高效,是因为其不可变性
  • String 类型:
    • 为了支持可变性,需要在 heap 上分配内存来保存编译时未知的文本内容:
    • 操作系统必须在运行时请求内存,这步通过调用 String::from 来实现
// 字符串字面值
let s = "Hello";

// 可变字符串(String类型)
let mut s = String::from("Hello"); // 调用String类型下的from函数创建一个String类型
s.push_str(", World");
println!("{}",s);

# 切片

let a = [1,2,3,4,5]
let slice = &a[1..3]

# 函数

# 结构

fn fn_name(x: i32, y: i64) { // 入参必须定义参数类型
    println!("the value og x is : {}", x);
}

# 表达式

fn fn2() {
    let x = 5;

    let y = {
        let x = 1;
        x + 3 // 注意这里是没有;的,有就返回`()`了
    };

    println!("y={}", y); // y=4

}

# 返回值

  • 在 -> 符合后面声明函数返回值的类型,但是不可以为返回值命名
  • 函数体最后一个表达式的值为返回值。大多数函数都是默认使用最后一个表达式为返回值
  • 若要提前返回,需要使用 return 关键字,并指定一个值;
fn five() -> i32 {
	5 // 返回5,主要这里没有;
}

# 注释

与 Java 类似

//
/* */

# 控制流

# if


let number = 3;

if number < 5 { // 必须是布尔类型,有别于js
	....
} else if number > 100 {

} else {
	....
}

因为 if 是一个表达式,所以可以将它放在 let 语句中等号的右边:

fn fn3() {
    let condition = true;
    let number = if condition { 5 } else { 6 }; // 类似于三元表达式
    println!("The value of number is: {}", number);
}

# loop

死循环,直到遇到 break

loop {
 ……
}


fn fn4() {
    let mut counter = 0;
    let result = loop {
        counter += 1;

        if counter == 10 {
            break counter * 2;
        }
    };

    println!("The result is {}", result);
}

# while

let mut numer = 3;
while numer != 0 {
    println!("{}", numer);

    numer = numer - 1;
}

println!("LIFTOFF!!!")

# for

# 遍历数组

let a = [1, 2, 3, 4, 5];
for element in a {
    println!("{}", element);
}

# 数值遍历

for number in (1..4) {
	// 遍历从1-3的值,不包括4
}

for number in (1..4).rev {
	// 遍历从3-1的值,不包括4
}

# 所有权

# Stack 和 Heap

  • Stack,栈,后进先出(LIFO)
  • 存储在 Stack 上的数据必须拥有已知的固定大小,反之则必须存储在 heap 上

# 数据存储

  • heap
    • 性能较差一些
    • 把数据插入 heap 时,会请求一定数量的空间
    • 操作系统在 heap 里找到一块足够大的空间,把它标记为在用,并返回一个指针,也就是这个空间的地址
    • 这个过程叫做在 heap 上进行分配
  • stack
    • 存储已知大小的数据
    • 把值压入 stack 上不叫分配
    • 指针是已知固定大小的,可以存放在 stack

  • 把数据压到 stack 上要比在 heap 上分配快得多:因为操作系统不需要寻找用来存储新数据的空间,那个位置永远都在 stack 的顶端。
  • 在 heap 上分配空间需要做更多的工作:操作系统首先需要找到一个足够大的空间来存放数据,然后要做好记录方便下次分配。

# 数据访问

  • stack 可直接读取
  • heap 的数据需要先在 stack 找到指针才能找到对应的 heap,因此会慢一些

# 所有权存在的原因

  • 追踪代码的哪些部分正在使用 heap 的哪些数据
  • 最小化 heap 上的重复数据量
  • 清理 heap 上未使用的数据以避免空间不足

# 所有权规则

  • 每个值都有一个变量,这个变量是该值的所有者
  • 每个值同时只能有一个所有者
  • 当所有者超出作用域(scope)时,该值将被删除

# 变量作用域

fn main(){
	// s 不可用
	let s = "hello"; // s 可用
	// 可以对 s 进行相关操作
} // s 作用域到此结束,s 不再可用

# 内存和分配

以 String 类型为例,当用完 String 之后,需要使用某种方式将内存返回给操作系统,这步:

  • 在拥有 GC 的语言中,GC 会跟踪并清理不再使用的内存,
  • 在没有 GC 的语言中,就需要我们去识别内存何时不再使用,并调用代码手动将它返回:
    • 如果忘了,那就浪费内存
    • 如果提前做了,变量就会非法
    • 如果做了两次,也是 Bug。必须一次分配对应一次释放
  • Rust 采用不同的方式:对于某个值来说,当拥有它的变量走出作用范围时,会自动调用 drop 函数,内存会立即自动的交换给操作系统。

# 变量和数据交互的方式

# 移动 - 复杂类型(类浅拷贝)

有别于其他语言的浅拷贝,Rust 采用一种名为移动(Move)的方式,如下文,变量 s1 赋值到 s2 后,s1 就失效了。

image.png

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

println!("{}", s1); // 这里会报错,s1已经移动到s2
  • 为了保证内存安全:
    • Rust 没有尝试复制被分配的内存
    • Rust 让 s1 失效:当 s1 离开作用域的时候,Rust 不需要释放任何东西
  • 隐含的一个设计原则:Rust 不会自动创建数据的深拷贝

# 复制 - 标量类型

let x = 5;
let y = x;
// 整数时已知且固定大小的简单的值,这两个5被压到了stack中

println!("{}, {}", x, y); // 不会报错

由于标量类型只作用在 Stack,因此 “移动” 并不会带来明显性能提升,因此采用了复制的方式处理。

Note

这个特性跟 Java 类似,基本类型传递值 a1 != b1 ,对象类型传递的是对象地址 a2 == b2 ,但 Rust 在传递对象地址时,会导致原变量失效。

# 克隆(深拷贝)

  • 即深拷贝
  • 只用实现了 Copy trait 的类型才可用
  • 如果一个类型或该类型的一部分实现了 Drop trait,那么 Rust 不允许它再实现 Copy trait image.png
let s1 = String::from("Hello");
let s2 = s1.clone();

println!("{}, {}", s1, s2); // 正常输出

Copy trait

  • 任何简单标量的组合类型都可以是 Copy 的
  • 任何需要分配内存或某种资源的都不是 Copy 的
  • 一些拥有 Copy trait 的类型:
    • 所有的整数类型:如 u32
    • bool
    • char
    • 所有的浮点类型
    • Tuple,如果其所有的字段都是 Copy 的
      • (i32,i32) 是
      • (i32,String) 不是

# 函数

在语义上,将值传递给函数和把值赋给变量是类似的,同样会发生移动或复制

# 传递

fn main(){
    let s = String::from("Hello, world!");

    take_ownership(s);

    // println!("{}", s); // 这一行会报错,因为 `s` 的所有权已经被转移到 `take_ownership` 函数中。

    let x = 5;

    make_copy(x);

    println!("{}", x); // 这一行不会报错,因为 `x` 是一个整数,它的值被复制到 `make_copy` 函数中,而不是转移所有权。
}

fn take_ownership(s: String) {
    println!("{}", s);
} // `s` 在这里被丢弃,内存被释放。

fn make_copy(x: i32) {
    println!("{}", x);
} // `x` 在这里被丢弃,但它的值已经被复制,所以不会影响原来的 `x`。

# 返回值

一个变量的所有权总是遵循同样的模式:

  • 把一个值赋给其他变量时就会发生移动
  • 当一个包含 heap 数据的变量离开作用域时,它的值就会被 drop 函数清理,除非数据的所有权移动到另一个变量上
fn main(){
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
}

fn gives_ownership() -> String {
    let some_string = String::from("hello");
    some_string // 这个函数将所有权转移给调用者
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string // 这个函数接收一个字符串并将其返回给调用者
}

# 引用与借用

如何让函数使用某个值,但不获得其所有权?

  • & 符号表示引用:允许你引用某个值而不取得其所有权
  • 把引用作为函数参数的行为叫做借用
  • 默认情况,借用变量不可修改
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()
}

image.png

可修改的借用

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

}

fn calculate_length(s: &mut String) -> usize {
	s.push_str(", world!");
    s.len()
}

可变引用

  • 在特定作用域内,对某一块数据,只能有一个可变的引用,因此可在编译时防止数据竞争。

  • 以下 3 种行为下会发生数据竞争:

    • 两个或多个指针同事访问同一个数据
    • 至少有一个指针用于写入数据
    • 没有任何机制来同步对数据的访问
  • 不可同时存在可变引用和不可变引用

fn main(){
    let mut s = String::from("hello");
    let r1 = &s; // no problem
    let r2 = &s; // no problem
    println!("{} and {}", r1, r2); // no problem
    let r3 = &mut s; // BIG PROBLEM
    println!("{}", r3); // BIG PROBLEM
}

悬空引用 Dangling References

  • 悬空指针(Dangling Pointer):一个指针引用了内存中的某个地址,而这块内存可能已经释放并分配给其他人使用了。
  • 在 Rust 里,编译器可保证引用永远都不是悬空引用:编译器将保证在引用离开作用域之前数据不会离开作用域。

# 字符串切片

  • 类型为 &str
  • 字符串字面值本质上是切片,是一个指向二进制程序特定位置的切片
  • 由于 &str 是不可变引用,所以字符串字面值也是不可变的
  • 形式: [开始索引..结束索引] ,边界值为 [开始索引,结束索引)
fn main(){
    let mut s = String::from("Hello World");

    let hello = &s[0..5]; // or let hello = &s[..5];
    let world = &s[6..11]; // or let world = &s[6..];

    println!("{} {}", hello, world);
}
fn main(){
    let mut s = String::from("Hello World");
    let wordIndex = first_word(&s);
	
	// s.clear(); // s不能再可变
    println!("The first word is at index: {}", wordIndex);
}

fn first_word(s: &String) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i]; // 返回到第一个空格的字符串切片
        }
    }

    &s[..] // 返回整个字符串切片
}

作为参数传递

  • 采用 &str 作为参数类型,可以同时接收 String 和 &str 类型的参数
  • 使用字符串切片 ( &str ) 代替字符串引用 ( &String ) 使 API 更加通用,且不会损失任何功能
fn main(){
    // This is a string slice
    let mut my_string = String::from("Hello world");
    let wordIndex = first_word(&my_string[..]);

    // This is a string literal
    let my_string_literal = "Hello world";
    let wordIndexLiteral = first_word(my_string_literal);
}

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

# 结构体 - struct

# 含义

  • 自定义的数据类型
  • 为相关联的值命名,打包 =》 有意义的组合

# 定义

struct User {
	username: String,
	email: String,
	sign_in_count: u64,
	active: bool, // 最后一个属性结尾也有逗号
}

# 实例化

// email = String::from("[email protected]");
let user1 = User {
	// 属性顺序无需跟定义的顺序相同;必须声明所有属性
	email: String::from("[email protected]")
	// email, // 与es6类似,当存在一个字段同名变量时,可简写
	username: String::from("Michael")
	active: true,
	sign_in_count: 556,
}

# 读写

  • 一旦 struct 实例是可变的,那么实例中所有的字段都是可变的。【这里有别于 Java 的 Class】
let mut user1 = User { ... }

user.email = String::from("[email protected]")

# 更新(实例局部克隆)

let user2 = User {
	username: String::from("user2"),
	email: String::from("[email protected]"),
	active: user1.active,
	sign_in_count: user1.sign_in_count,
};

优化写法:

let user2 = User {
    username: String::from("user2"),
    email: String::from("[email protected]"),
    ..user1 // 剩下的字段由user1提供
};

# Tuple struct

相当于给某个格式的元组 (tuple) 起名

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

# Unit-Like Struct(空结构体)

  • 没有定义任何字段的 struct,叫做 Unit-Like struct
  • 适用于需要再某个类型上实现某个 trait,但又不想存储字段的数据

# 所有权

  • struct 里定义的字段为非引用类型时,实例拥有其所有权,只要 struct 实例有效,字段数据就有效。
  • struct 也可以定义引用类型,但需要使用生命周期(见下文);
    • 生命周期可以保证实例是有效的,引用也是有效的。
    • 如果没有使用生命周期,就会报错。

# 打印 struct

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn area(rectangle: &Rectangle) -> u32 {
    rectangle.width * rectangle.height
}

println!("{}", rect); // 需要实现std::fmt::Display,自定义struct没有实现
println!("{:?}", rect); // 需要实现Debug,或在结构体上添加`#[derive(Debug)]`

# 实例方法

# 定义

  • 这里所说的 方法 ,有别于 函数 ,用 Java 的概念来说,就是实例方法。
  • 与 函数 相同之处:fn 关键字、名称、参数、返回值
  • 与 函数 不同之处:
    • 方法是在 struct(或 enum、trait 对象)的上下文中定义
    • 第一个参数是 self ,表示方法被调用的 struct 实例,可获得其所有权(去掉 & )或可变借用( mut )
  • 同一个 struct 的 impl 块可以有多个
impl Rectangle {
    fn area(&self) -> u32 { // 入参使用引用,也可以不加&获得所有权,无限制
        self.width * self.height
    }
}

# 调用

  • Rust 会自动引用或解引用,即无需 *obj
  • 调用方式时,Rust 会根据情况自动添加 & 、 &mut 、 * ,以便 object 可以匹配方法的签名
// 下面两行代码等效
p1.distance(&p2);
(&p1).distance(&p2);

# 参数

可添加出 &self 以外的入参

impl Rectangle {
    ...

    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

# 关联函数(静态方法)

  • 不把 self 作为第一个参数的函数,叫关联函数(不是方法),可理解为静态方法,不是实例方法。
  • 多用于作为构造器
  • 例如: String::from()

# 定义

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

# 调用

  • 使用 :: 符号调用, :: 还可用于模块创建的命名空间
let s = Rectangle::square(20);

# 枚举

# 定义

enum IpAddrKind {
    V4,
    V6,
}

let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
enum IpAddrKind {
    V4(u8, u8, u8, u8),
    V6(String),
}

let four = IpAddrKind::V4(127, 0, 0, 1);
let six = IpAddrKind::V6(String::from("::1"));

# 调用

fn route(ip_kind: IpAddrKind) {
    match ip_kind {
        IpAddrKind::V4 => println!("IPv4"),
        IpAddrKind::V6 => println!("IPv6"),
    }
}

route(IpAddrKind::V4);

# 定义方法

与 struct 类似,略

# Option 枚举

  • Rust 没有 Null,Rust 的作者认为空指针成本是巨大的,更优雅的方法是使用 Option<T> 来处理,值得一提的是,Java 也在 Java8 引入了这一特性。
  • 它包含在 Prelude(预倒入模块中),可直接使用
enum Option<T>{
	Some(T),
	None,
}

// 可直接使用`Some()`无需前缀`Option::`
let some_number = Some(5);
let some_string = Some("a string");

// 由于None没有入参,无法推断类型,需要显示声明
let absent_number: Option<i32> = None;
上次更新: 2025/05/10, 02:23:42
入门
常见库(Crate)

← 入门 常见库(Crate)→

最近更新
01
磁盘管理与文件系统
05-02
02
网络测试 - iperf3
05-02
03
Docker Swarm
04-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 NipGeihou | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式