news 2026/9/6 12:26:32

【Rust入门知识点学与练】第15课:泛型 Generics

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Rust入门知识点学与练】第15课:泛型 Generics

引语

根据之前的课程进度,第15课是泛型 Generics。这是一个文本创作类任务,我需要按照之前课程的格式来编写泛型的教学内容,包括知识点讲解、代码示例和练习题。

知识点1:泛型函数

泛型让你写出适用于多种类型的代码,避免重复:

// 不用泛型:需要写多个函数fnlargest_i32(list:&[i32])->i32{letmutlargest=list[0];for&itemin&list[1..]{ifitem>largest{largest=item;}}largest}// 用泛型:一个函数搞定所有类型// T: PartialOrd 表示 T 必须支持比较操作fnlargest<T:PartialOrd>(list:&[T])->&T{letmutlargest=&list[0];foritemin&list[1..]{ifitem>largest{largest=item;}}largest}fnmain(){letnumbers=vec![34,50,25,100,65];println!("最大数: {}",largest(&numbers));letchars=vec!['y','m','a','q'];println!("最大字符: {}",largest(&chars));letfloats=vec![1.1,3.3,2.2];println!("最大浮点: {}",largest(&floats));}

知识点2:泛型结构体

// 泛型结构体:Point 可以存储任何类型的坐标structPoint<T>{x:T,y:T,}impl<T>Point<T>{fnnew(x:T,y:T)->Self{Point{x,y}}fnx(&self)->&T{&self.x}fny(&self)->&T{&self.y}}// 可以为特定类型实现额外方法implPoint<f64>{fndistance_from_origin(&self)->f64{(self.x.powi(2)+self.y.powi(2)).sqrt()}}fnmain(){letinteger_point=Point::new(5,10);println!("整数点: ({}, {})",integer_point.x(),integer_point.y());letfloat_point=Point::new(3.0,4.0);println!("浮点点: ({}, {})",float_point.x(),float_point.y());println!("到原点距离: {}",float_point.distance_from_origin());// 5.0}

知识点3:多个泛型参数

structPair<T,U>{first:T,second:U,}impl<T,U>Pair<T,U>{fnnew(first:T,second:U)->Self{Pair{first,second}}}impl<T:std::fmt::Display,U:std::fmt::Display>Pair<T,U>{// 只有当 T 和 U 都实现了 Display 时,这个方法才可用fndisplay(&self){println!("({}, {})",self.first,self.second);}}fnmain(){letp=Pair::new("hello",42);p.display();// (hello, 42)letp2=Pair::new(3.14,true);p2.display();// (3.14, true)}

知识点4:trait 约束

用 where 子句让约束更清晰:

usestd::fmt;// 普通写法fnnotify1<T:fmt::Display+Clone>(item:T){println!("通知: {}",item.clone());}// where 写法(更清晰)fnnotify2<T,U>(t:T,u:U)whereT:fmt::Display+Clone,U:fmt::Debug+Clone,{println!("T: {}, U: {:?}",t.clone(),u.clone());}fnmain(){notify1(String::from("hello"));notify2(String::from("hello"),42);}

知识点5:返回实现了 trait 的类型

usestd::fmt;// 返回实现了 Display 的任意类型fnreturns_displayable(flag:bool)->implfmt::Display{ifflag{String::from("hello")}else{42}// 注意:上面这样写会报错!// impl Trait 要求返回类型必须一致// 正确写法如下:}// 正确:两个分支返回相同类型fnreturns_string(flag:bool)->implfmt::Display{ifflag{String::from("true")}else{String::from("false")}}fnmain(){println!("{}",returns_string(true));println!("{}",returns_string(false));}

核心规则

概念 写法
泛型函数 fn func<T: Trait>(param: T)
泛型结构体 struct Name { field: T }
多泛型参数 struct Name<T, U>
trait 约束 T: Display + Clone
where 子句 where T: Display, U: Debug
返回 trait 类型 fn func() -> impl Trait
调用泛型函数 类型自动推断,或 func::(…)

动手试试

补全下面的代码:

usestd::fmt;// 补全:定义一个泛型结构体 Stack<T>,内部用 Vec<T> 存储数据// 实现以下方法:// 1. new() -> Stack<T> 创建空栈// 2. push(&mut self, item: T) 入栈// 3. pop(&mut self) -> Option<T> 出栈// 4. peek(&self) -> Option<&T> 查看栈顶(不弹出)// 5. is_empty(&self) -> bool 是否为空// 6. size(&self) -> usize 栈中元素个数// 补全:为 Stack<T> 实现 Display trait(T 需要实现 Display)// 格式:[底 -> 顶],例如栈中有 1,2,3(3是栈顶),输出 "[1, 2, 3]"fnmain(){// 补全:创建一个整数栈,依次压入 10, 20, 30// 打印栈内容(期望: [10, 20, 30])// 打印栈大小(期望: 3)// 查看栈顶(期望: Some(30))// 弹出栈顶并打印(期望: 30)// 再次打印栈内容(期望: [10, 20])// 弹出所有元素,再打印是否为空(期望: true)// 补全:创建一个字符串栈,压入 "hello", "world"// 打印栈内容(期望: [hello, world])}

期望输出:
[10, 20, 30]
大小: 3
栈顶: Some(30)
弹出: 30
[10, 20]
栈为空: true
[hello, world]

写好了贴过来,没问题就进入第16课:生命周期 Lifetimes 👋

贴代码

usestd::fmt;// 泛型栈结构体structStack<T>{data:Vec<T>,}// 方法实现impl<T>Stack<T>{// 1. 创建空栈fnnew()->Stack<T>{Stack{data:Vec::new()}}// 2. 入栈fnpush(&mutself,item:T){self.data.push(item);}// 3. 出栈fnpop(&mutself)->Option<T>{self.data.pop()}// 4. 查看栈顶(借用,不获取所有权)fnpeek(&self)->Option<&T>{self.data.last()}// 5. 判断栈是否为空fnis_empty(&self)->bool{self.data.is_empty()}// 6. 获取栈元素数量fnsize(&self)->usize{self.data.len()}}// 实现 Display,约束 T 必须实现 fmt::Displayimpl<T:fmt::Display>fmt::DisplayforStack<T>{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{// 将每个元素转字符串,用 ", " 连接letitems:Vec<String>=self.data.iter().map(|x|format!("{}",x)).collect();write!(f,"[{}]",items.join(", "))}}fnmain(){// 整数栈测试letmutint_stack=Stack::new();int_stack.push(10);int_stack.push(20);int_stack.push(30);println!("{}",int_stack);println!("栈大小: {}",int_stack.size());println!("栈顶: {:?}",int_stack.peek());lettop=int_stack.pop();println!("弹出: {:?}",top);println!("{}",int_stack);// 清空栈int_stack.pop();int_stack.pop();println!("栈是否为空: {}",int_stack.is_empty());// 字符串栈测试letmutstr_stack=Stack::new();str_stack.push("hello");str_stack.push("world");println!("{}",str_stack);}

运行输出

plaintext

[10, 20, 30]
栈大小: 3
栈顶: Some(30)
弹出: Some(30)
[10, 20]
栈是否为空: true
[hello, world]

核心知识点讲解

1. 泛型 Stack

T 是类型参数,栈可以存放任意类型: Stack 、 Stack<&str> 、 Stack 。
泛型让一份代码支持多种数据类型,不需要重复写多份栈实现。

2. Vec 作为栈底层存储

  • push :尾部追加 → 入栈;
  • pop :尾部删除 → 出栈;
  • last() :获取尾部元素的引用 → peek,不会拿走所有权。

3. 借用 vs 所有权

  • pop(&mut self) -> Option :返回元素本身,所有权转移,栈里面就没有了;
  • peek(&self) -> Option<&T> :返回引用,栈里面元素还在,只是看一看。

4. Display trait 实现

impl<T:fmt::Display>fmt::DisplayforStack<T>

T: fmt::Display 是trait 约束:只有栈里面的元素类型T支持Display,Stack才能被 {} 打印。
如果T没有实现Display,就不能用println!(“{}”, stack),编译报错。

5. 迭代器 map + collect

self.data.iter().map(|x|format!("{}",x)).collect()

遍历栈里每一个元素,转为字符串,收集到一个字符串Vec,最后用 join 拼接。

拓展:

Rust标准库没有单独的Stack容器,日常开发直接用Vec模拟栈(push/pop/last),和我们这道题一模一样。

做完泛型栈,泛型基础就过关了。需要一道泛型队列Queue练习题吗?

练习题:泛型队列 Queue(FIFO 先进先出)

题目要求

使用 Rust 实现一个泛型队列 Queue,底层用 Vec 存储,实现队列的基础操作。

队列:先进先出(FIFO),队尾入队,队头出队。

1. 结构体定义

Queue ,内部字段 data: Vec

2. 需要实现的方法

2.1 new() -> Self :创建空队列
2.2 enqueue(&mut self, item: T) :入队,元素加到队尾(vec.push)
2.3 dequeue(&mut self) -> Option :出队,从队列头部删除并返回

注意:Vec 的 remove(0),删除索引0的元素;数组头部删除性能差,但初学练习就用这个简单实现

2.4 front(&self) -> Option<&T> :查看队首元素(不删除,返回引用)
2.5 is_empty(&self) -> bool :队列是否为空
2.6 len(&self) -> usize :返回队列元素个数

3. 实现 Display trait

约束: T: fmt::Display
打印格式样例:队列存 10,20,30(10队首、30队尾)→ [10, 20, 30]

4. main 测试逻辑

  • 创建一个 i32 队列,依次入队:10、20、30
  • 打印队列内容 → [10, 20, 30]
  • 打印队列长度 → 3
  • 获取队首 front → Some(10)
  • 出队 dequeue,打印弹出的值 → 10
  • 打印当前队列 → [20, 30]
  • 全部元素出队,打印 is_empty → true
  • 新建字符串队列,入队 “a” , “b” ,打印队列 → [a, b]

提示

4.1. vec.remove(0) :移除第一个元素,返回该元素;队列为空调用remove(0)会panic,不能直接用,需要先判断队列是否为空再返回Option。
4.2. 获取队首引用: self.data.first()
4.3. Display 的实现思路和上一题 Stack 几乎一样。

贴代码

先自行编写,完成后再看参考答案👉完整参考答案

usestd::fmt;structQueue<T>{data:Vec<T>,}impl<T>Queue<T>{fnnew()->Self{Queue{data:Vec::new()}}// 入队:队尾添加fnenqueue(&mutself,item:T){self.data.push(item);}// 出队:队头弹出,空队列返回Nonefndequeue(&mutself)->Option<T>{ifself.is_empty(){None}else{Some(self.data.remove(0))}}// 获取队首元素的引用fnfront(&self)->Option<&T>{self.data.first()}fnis_empty(&self)->bool{self.data.is_empty()}fnlen(&self)->usize{self.data.len()}}// Display 实现impl<T:fmt::Display>fmt::DisplayforQueue<T>{fnfmt(&self,f:&mutfmt::Formatter<'_>)->fmt::Result{letstrs:Vec<String>=self.data.iter().map(|v|format!("{}",v)).collect();write!(f,"[{}]",strs.join(", "))}}fnmain(){// i32队列测试letmutq=Queue::new();q.enqueue(10);q.enqueue(20);q.enqueue(30);println!("{}",q);println!("队列长度: {}",q.len());println!("队首: {:?}",q.front());letout=q.dequeue();println!("出队元素: {:?}",out);println!("{}",q);q.dequeue();q.dequeue();println!("队列是否为空: {}",q.is_empty());// 字符串队列letmutstr_q=Queue::new();str_q.enqueue("a");str_q.enqueue("b");println!("{}",str_q);}

运行输出

plaintext

[10, 20, 30]
队列长度: 3
队首: Some(10)
出队元素: Some(10)
[20, 30]
队列是否为空: true
[a, b]

知识点总结

  1. 栈(Stack):LIFO后进先出,push/pop操作vec尾部,性能很好 O(1)
  2. 本版队列: remove(0) ,数组头部删除,时间复杂度O(n),元素很多的时候很慢;工业级实现一般用双端队列VecDeque( std::collections::VecDeque ),两端增删都是O(1)。
  3. first() :获取第一个元素的引用,对应栈的 last() 。
  4. 泛型代码复用:Queue一份实现,支持i32、&str、String等任意类型。

拓展

标准库双端队列 VecDeque 的基础用法(工业版队列)

usestd::collections::VecDeque;fnmain(){letmutdq=VecDeque::new();dq.push_back(10);// 入队尾部dq.pop_front();// 队头出队,O(1)高性能!}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/6 12:22:44

矩阵拼团系统设计:先付款先排队的订单排序与自动返本算法

技术摘要 矩阵拼团通过"先付款先排队"的订单时间戳排序&#xff0c;实现消费者返本与商家快速清库存的双赢。消费者付款后按时间精确排队&#xff0c;后续订单触发前面的订单返本&#xff0c;形成"人人有回报"的机制。本文从排队算法视角&#xff0c;拆解矩…

作者头像 李华
网站建设 2026/9/6 12:22:15

RTX 4090上2小时从零训练64M中文小模型全记录

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 12:18:03

PTP授时服务器时间溢出解析:从原理到部署排查

1. 问题背景&#xff1a;PTP授时服务器也会被“时间溢出”咬一口PTP&#xff08;Precision Time Protocol&#xff0c;精确时间协议&#xff09;这几年在广播、专业音视频、工业控制、数据采集里基本成了标配。以前大家习惯把PTP授时服务器当成“一个会输出高精度时间的黑盒子”…

作者头像 李华
网站建设 2026/9/6 12:13:17

聚搜云:8卡H100服务器适合什么业务?企业真的需要8张GPU吗

聚搜云&#xff3b;JuSouYunClouD -聚搜云&#xff08;深圳&#xff09;信息有限公司&#xff3d;可根据企业实际业务需求、模型规模、显存要求、GPU数量及部署场景&#xff0c;提供相关大厂GPU云服务器、GPU算力及企业级计算产品&#xff0c;覆盖&#xff1a;V100、T4、A10、A…

作者头像 李华
网站建设 2026/9/6 12:12:09

从零搭建全能Agent:腾讯云AI Skills实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/6 12:10:44

高分辨率工业相机如何赋能缺陷检测?从Libra 27105看选型要点

1. 工业检测产品线扩充&#xff0c;为什么比单纯发一款相机更有看头这几年做机器视觉集成的人应该都有个感受&#xff1a;工业检测项目越来越难用同一套硬件方案打天下了。锂电、半导体、面板、PCB&#xff0c;每个行业对缺陷类型、分辨率、帧率的要求都不一样&#xff0c;有的…

作者头像 李华