rust拾遗

一.万物都是表达式,rust代码就是由表达式构成
1.普通的表达式 能返回运算的值
2.语句也算表达式 返回“()”值 ( 这里的“()"是一种类似null的值, 类型为(),值为() )
3.块表达式 用{} 包起来的,这种表达式的值 就是语句块花括号内 最后表达式 的值。这类表达值能返回值,能用来赋值
例如 let x = {
println!("hi");
25
}

这里的x 就是 25, 如果25这里加分号则返回 ()。任何语句都是舍弃运算值 来返回空值

其次,if语句这种来块表达式的也能赋值
let y = if x > 25 {
println!("2nd");
30
}
这里的y就是30

3.复合类型
image

4.模式匹配
不是只有match表达式才算模式匹配,let 赋值过程也算模式匹配,有些match匹配方法也能用在这上面(模式匹配过程的结构,还有各种各种的占位符)

image
image

5.关于Trait的二三事
Trait 有点像 接口

类似 Trait Speak {
fn speak(&self);
type A;
}

放的是函数签名(签名第一个参数可以放self,所以像方法)
fn speak(&self);
也能放声明 关联类型
type A
(关联类型就是在Trait或者impl块里面给对某个类型进行重命名,然后后续都可以将此用在impl块方法上)。

注意的是, Trait里的方法可以有默认值,也就是默认的实现,
如下:
Trait Speak {
fn speak(&self){
println("gg");
};
type A = int;
}
然后实现该Trait的类型默认有该方法,不满意时也可以在impl的时候重写同名方法。

image
image

如果一个Trait 继承了某个Trait(Trait t1:t2+t3) 那么给类型实现t1的时候 也要实现t2 t3

然后又发现一个点
Q:trait 继承 和 泛型约束 语法很像, 都是trait xx :t1 + t2 <T : t1 + t2> 到底是不是一个东西
A:不是同一个东西,只是语法上用了同一个 + 符号,但语义不同:

  • trait 继承 trait A: B + C — 定义一个 trait A,规定:要实现 A,必须先实现 B 和 C。这是 trait 定义层面的超类约束,影响的是谁能实现这个 trait。
  • 泛型约束 fn foo<T: B + C>(t: T) — 在使用时约束:这个泛型参数 T 必须同时实现 B 和 C。这是函数/类型使用层面的类型参数约束,影响的是谁能调用这个函数。

2.如果给某个struct泛型了 加了特征约束,impl 生命时也要在impl<>这个括号给T加上约束

3。trait的多态应用,动态分发和 静态分发 (这里讨论的是 dyn Trait 类型 或者 impl Trait 类型 来接收实例。)

先说动态分发,类似C++,编译完后底层 根据每个实现该trait(C++接口叫接口)的实例类 都会生成一个虚表,虚表共享在每个具体实例身上的,由虚表指针寻址,实例隐式自带这个指针.

动态分发实际就是 ①底层生成虚表 然后 ②对象实例虚表指针指向响应虚表, ③接口变量 就是 即使存放不同的实例,但可以通过实例自身的虚表指针来实现调用方法 机制。 C++ 底层默认的多态都是这种,是有开销的 虚表 这个函数指针数组是实际存在的,只要不同实现接口的类型越多,虚表 这个数组再底层的定义也越多,会有内存占用。

回到rust上,
当某个 trait 对象 (接口类型变量) 接受 一个实例时,调用这些方法 由于编译不知具体调用哪个实例的方法,所以调用方法会通过虚表指针来调用。

这里rust的动态分发是 用 &dyn Trait 作为类型 来接收实例, 作为返回值类型时 往往用Box<&dyn Trait>来包一层,如果returen多种不同类型的值。用这个返回值类型接收会报错。 因为返回值类型不确定 长度不统一,编译过不了。用box包了就是返回一个固定的指针类型(4字节长度)。

静态分发
C++有个CRTP的概念。但是我不熟,这里不展开,也是实现了静态分发的机制。
这里rust的静态分发是 用 impl Trait 作为类型 接收时,编译器 编译的时候会将impl Trait好像会自动替换会实际的类型(只要 返回值类型是实现了该Trait的类型,就会替换),算是零成本开销 。

而dyn Trait 这种动态分发 多走了一层虚表来找方法再调用,增加了开销。

生命周期

// 返回值不是引用,不用标
fn process(x: &str) -> String {
String::from(x) // ✅ 返回 String,不用标
}

// 返回值是引用,需要标
fn process(x: &str) -> &str {
x // ✅ 自动推断,不用手动标
}

// 多个引用参数,返回引用,需要标
fn first<'a>(x: &'a str, y: &str) -> &'a str {
x // ✅ 手动标
}

'a标注告诉编译器:返回值 和 参数x 活得一样久

个人拙见: 返回值有引用的时候, 如果这个返回值跟某个 参数引用有关 ,那么他们就是用一个 生命周期,都要标`a,证明他们能活一样久,
不会有那种x 引用的原变量销毁了 ,导致 悬垂引用:
具体如下:
Q:为什么返回值生命周期跟参数有关?
A:
fn first<'a>(x: &'a str, y: &str) -> &'a str {
x // 返回的是 x 的数据
}

// 问题:如果 x 在函数结束后被释放,返回的引用指向谁?
let result;
{
let s1 = String::from("hello");
let s2 = String::from("world");
result = first(&s1, &s2); // 返回 s1 的引用
}
// s1 已经释放了
println!("{}", result); // ❌ 悬垂引用!
生命周期的作用:告诉编译器"返回的引用和谁活得一样久"
fn first<'a>(x: &'a str, y: &str) -> &'a str {
x // 返回值和 x 活得一样久
}

// 编译器检查:result 能活多久?
let result;
{
let s1 = String::from("hello");
result = &s1; // ❌ 编译器报错:s1 活得不够久
}
println!("{}", result); // ❌ 悬垂引用!

闭包 有点像匿名函数,但它更像一个表达式,可以赋值给变量
普通函数 fn(x :i32,y :i32)-> i32{
x+y
}

闭包函数 去掉(fn 圆括号变为竖线)let a = |x :i32,y :i32|->i32{ x+y };
1.可以定义闭包的执行直接执行
|x :i32,y :i32|->i32{ x+y }(x,y)_

2.可省略 返回值 参数类型 让编译来推导,如果语句只有一句表达式 花括号也能省略,例 |x,y| x+y
image

模板匹配
具名结构体也能解构。
struct User { name: String, age: u32 }

let user = User { name: "Tom".into(), age: 25 };

// 解构
let User { name, age } = user;
// name = "Tom", age = 25

// 只取部分字段
let User { name, .. } = user;
// name = "Tom"

// 重命名
let User { name: my_name, .. } = user;
// my_name = "tom"
三种结构体都能解构
// 1. 元组结构体
struct Point(i32, i32);
let Point(x, y) = Point(1, 2);

// 2. 具名结构体
struct User { name: String, age: u32 }
let User { name, age } = User { name: "Tom".into(), age: 25 };

// 3. 单元结构体(没法解构,没字段)
struct Marker;
let m = Marker;
枚举也能解构
enum Shape {
Circle(f64),
Rect { w: f64, h: f64 }, // 枚举变体也能用具名字段
}

let s = Shape::Rect { w: 10.0, h: 20.0 };

match s {
Shape::Circle(r) => println!("半径 {r}"),
Shape::Rect { w, h } => println!("{w} x {h}"), // 具名解构
}
Rust 里任何复合类型都能解构,和是元组还是具名无关。

这里可以看到结构就是在左值 或者参数 里用 结构名{x,y}或者枚举名::变体名(x) 来提取字段值或者变体存放的值,就是怎么定义结构体没具体 就用什么方式将变量 解构出来。

posted @ 2026-07-07 13:35  lostkk  阅读(8)  评论(0)    收藏  举报