跳到主要内容

泛型

泛型

泛型定义的是操作,而不是数据。它们允许我们编写适用于多种类型的代码,而不需要重复编写相似的代码。

Rust 泛型的关键要点 泛型带来灵活性:泛型是 Rust 中的一个基本概念,它允许你编写可以抽象地操作不同具体类型的代码。

类型占位符:这些是具体类型的替代词,通常用大写字母书写(例如T,,,,,)。EUAB

单态化:编译包含泛型的 Rust 代码时,编译器会执行单态化。这意味着它会为每个具体类型生成特定的泛型代码版本。例如,如果您使用 Vec<i32>Vec<String>,编译器会为 i32String 分别生成专门的代码。此过程确保在 Rust 中使用泛型不会像手动编写专门代码那样带来运行时性能损失。

定义泛型:您可以使用尖括号 <...> 来声明类型参数,从而定义泛型枚举、结构体、函数和方法。

增强可重用性:泛型的主要优势在于能够编写高度可重用和可维护的代码组件,这些组件不依赖于特定类型,从而减少重复并改善代码组织。

pub fn first<A, B>(t: (A, B)) -> A {  //类型全都用A和B来代指
t.0
}

pub fn last<A, B>(t: (A, B)) -> B {
t.1
}

#[derive(Debug)]
pub struct Rectangle <T = u32>{
pub top: T,
pub left: T,
pub width: T,
pub height: T,
}

结构体方法

#![allow(unused)]

#[derive(Debug)]
struct Point {
x: f32,
y: f32,
}

impl Point {
// Static method - associated function
// Used as a constructor to create new Point instances
fn new(x: f32, y: f32) -> Self {
Self {
x, // Shorthand for x: x
y, // Shorthand for y: y
}
}

// Instance method
// Modifies the Point instance it's called on
fn move_to(&mut self, x: f32, y: f32) {
self.x = x;
self.y = y;
}
}

fn main() {
// Create an instance using the 'new' static method
let mut p = Point::new(0.0, 0.0);

// Call the 'move_to' instance method to modify 'p'
p.move_to(1.0, 2.0);

// Print the modified point
println!("point: {:?}", p);
}

struct:通过捆绑相关数据字段来定义自定义数据类型。

impl:用于定义实现块的关键字,其中声明了类型的方法和相关函数。

方法(实例方法) :与类型实例关联的函数。它的第一个参数是 __init__ self、&self__init__ 或&mut self__init__,表示实例。使用点号表示法调用: __init__ instance.method_name()。

静态方法(关联函数) :与类型本身关联的函数,而非特定实例。它不接受self参数。使用类型名称和双冒号调用:TypeName::function_name()。常用于构造函数(例如,new)。

self(小写):实例方法中的特殊关键字,用于引用正在调用该方法的特定实例。

Self(大写):代码块内的特殊类型别名impl,指代impl代码块所属的类型(例如,Point在我们的示例中)。

&mut self:一种常见的模式,用于需要修改实例数据的实例方法的第一个参数。它提供对实例的可变引用。

#[derive(Debug)]:一个方便的属性,用于自动实现该Debug特性,从而可以轻松打印结构体实例以进行调试。

trait

trait是Rust中的一个核心概念,它定义了一组方法签名,任何类型都可以实现这些方法,从而实现trait。trait提供了一种抽象的方式来定义共享行为,使得不同类型可以通过实现相同的trait来共享功能。这类似于其他语言中的接口或抽象类,但在Rust中,trait还支持默认方法实现和泛型参数,使得它非常灵活和强大。通过trait,Rust实现了多态性,使得代码更加模块化和可重用。

就是通过trait关键字来定义一些属性方法。然后用impl应用到其他结构上,使其他结构也具有trait所定义的属性。

pub trait Iterator<T> {
fn next(&mut self) -> Option<&T>;
}

pub struct TupleIter<T> {
pub tuple: (T, T, T),
pub next: usize,
}

pub struct VecIter<T> {
pub vec: Vec<T>,
pub next: usize,
}

impl<T> Iterator<T> for TupleIter<T> {
fn next(&mut self) -> Option<&T> {
let next = self.next;
self.next += 1;

match next {
0 => Some(&self.tuple.0),
1 => Some(&self.tuple.1),
2 => Some(&self.tuple.2),
_ => None,
}
}
}

impl<T> Iterator<T> for VecIter<T> {
fn next(&mut self) -> Option<&T> {
let next = self.next;
self.next += 1;
self.vec.get(next)
}
}

特性边界

use std::cmp::PartialOrd; // Import the PartialOrd trait

fn max<T: PartialOrd>(x: T, y: T) -> T {
if x >= y { // Now valid, as T is guaranteed to implement PartialOrd
y
} else {
x
}
}

// Trait bounds
// x must implement A
fn c<T: A>(x: T) {} //直接指定

// Multiple trait bounds
// x must implement A and B
fn m<T: A + B>(x: T) {} // 必须是A和B的实现者,同时拥有A和B的属性

// Where clause
// x must implement A and B
// y must implement C
fn w<T, U>(x: T, y: U) // 用wehre表达更复杂的属性
where
T: A + B,
U: C,
{
}

用where子句来指定特性边界

fn example<T, U>(t_val: T, u_val: U)
where
T: TraitX + TraitY,
U: TraitZ,
{ /* ... */ }

生命周期

无处不在的特性: Rust 中的每个引用都具有其固有的生命周期。

内存安全基石:生命周期是 Rust 的编译时机制,用于防止悬空引用,从而在没有垃圾回收器的情况下保证内存安全。

推断和显式:虽然编译器通常可以推断生命周期(生命周期省略),但当引用生命周期之间的关系不明确时,显式注释就变得必要了,尤其是在涉及引用的函数签名和包含引用的结构中。

最终目标:生命周期系统的目标是确保任何参考数据所指向的数据,只要该参考数据还在使用,就保持有效。

编译器作为你的向导: Rust 编译器是不可或缺的助手。当生命周期注解缺失或不一致时,它会报错,引导你找到正确且安全的解决方案。

关联生命周期:显式生命周期注释的主要功能是定义不同引用的生命周期之间的关系,特别是输入参数的生命周期与返回引用的生命周期之间的关系。

返回值约束:当函数返回一个引用时,该引用的生命周期必须来源于某个输入参数,或者被指定为 return 'static。例如,它不能引用函数内部创建的局部变量,因为该变量的内存会在函数结束时被释放。

迭代器

任何实现了Iteratortrait的类型都可以迭代器。

into_iter()消耗迭代器,返回所有权。

let vals: Vec<u32> = vec![1, 2, 3];
for v: u32 in vals.into_iter() { // v is of type u32
println!("Value: {}", v);
}

iter()返回一个不可变引用的迭代器。

let vals: Vec<u32> = vec![1, 2, 3];
for v_ref: &u32 in vals.iter() { // v_ref is of type &u32
println!("Reference to value: {}", v_ref);
}
// `vals` is still available here.
println!("Original vector after iter(): {:?}", vals);

iter_mut()返回一个可变引用的迭代器。

let mut vals: Vec<u32> = vec![1, 2, 3]; // vals needs to be mutable
for v_mut_ref: &mut u32 in vals.iter_mut() { // v_mut_ref is of type &mut u32
*v_mut_ref *= 2; // Modify the value by dereferencing
}
// `vals` is still available here, and its elements are modified.
println!("Modified vector after iter_mut(): {:?}", vals); // Output: [2, 4, 6]

总结

默认的for v in collection循环语法会隐式地使用`const` 语句into_iter(),该语句会通过转移集合的所有权来消耗该集合。这会阻止在原始作用域内对该集合的进一步使用。

要多次遍历集合或在迭代后保留所有权,iter()请显式地使用不可变访问(yielding &T)或iter_mut()可变访问(yielding &mut T)。

使用时iter_mut(),请确保集合本身声明为可变的(let mut)。

选择正确的迭代器方法(into_iter()`__init__`、iter()`__return` 或 `__return` iter_mut())取决于你是需要消费集合、仅仅读取其元素,还是就地修改它们。这一选择直接影响你与 Rust 所有权系统的交互方式。

迭代器适配器

map():将一个函数应用于迭代器的每个元素,返回一个新的迭代器。

let squared: Vec<u32> = nums.iter().map(|x| x * x).collect();
println!("Squared numbers: {:?}", squared); // Output: [1, 4, 9]

filter():根据一个谓词函数过滤迭代器的元素,返回一个新的迭代器。

let even: Vec<u32> = nums.iter().filter(|&x| x % 2 == 0).cllect();
println!("Even numbers: {:?}", even); // Output: [2, 4]

clolect():将迭代器转换为一个集合类型(如Vec、HashSet等)。

let squared: Vec<u32> = nums.iter().map(|x| x * x).collect();
println!("Squared numbers: {:?}", squared); // Output: [1, 4, 9]

fold():将一个二元函数应用于迭代器的元素,累积结果,返回一个单一的值。

let sum: u32 = nums.iter().fold(0, |acc, &x| acc + x);
println!("Sum of numbers: {}", sum); // Output: 10

zip():将两个迭代器组合成一个迭代器,返回一个包含来自两个迭代器的元素对的迭代器。

let nums2 = vec![4, 5, 6];
let zipped: Vec<(u32, u32)> = nums1.iter().zip(nums2.iter()).map(|(&x, &y)| (x, y)).collect();
println!("Zipped numbers: {:?}", zipped); // Output: [(1, 4), (2, 5), (3, 6)]
📢 Share this article