Skip to content

feat: Add exercises for new Rust features (1.51+, 1.65+, 1.75+) #2248

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ rustlings-macros = { path = "rustlings-macros", version = "=6.4.0" }
serde_json = "1.0"
serde.workspace = true
toml_edit.workspace = true
tokio = { version = "1.36", features = ["full"] }

[target.'cfg(not(windows))'.dependencies]
rustix = { version = "1.0", default-features = false, features = ["std", "stdio", "termios"] }
Expand Down
67 changes: 67 additions & 0 deletions exercises/24_new_features/01_const_generics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// const_generics.rs
//
// Const 泛型允许我们使用编译时常量作为泛型参数。这在处理固定大小的数组和其他
// 需要编译时常量的场景特别有用。
//
// 在这个练习中,我们将实现一个简单的固定大小数组包装器,它可以安全地访问数组元素
// 并提供一些实用的方法。

#![allow(dead_code)]

// TODO: 实现一个泛型结构体 FixedArray<T, const N: usize>
// 它应该包装一个固定大小的数组 [T; N]
struct FixedArray<T, const N: usize> {
data: [T; N],
}

impl<T, const N: usize> FixedArray<T, N> {
// TODO: 实现 new 方法,它接受一个数组并返回 FixedArray
fn new(arr: [T; N]) -> Self {
todo!("创建一个新的 FixedArray 实例")
}

// TODO: 实现 get 方法,它安全地返回索引处的元素引用
fn get(&self, index: usize) -> Option<&T> {
todo!("返回指定索引处的元素,如果索引越界则返回 None")
}

// TODO: 实现 len 方法,返回数组的长度
fn len(&self) -> usize {
todo!("返回数组的长度")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_fixed_array() {
let arr = FixedArray::new([1, 2, 3, 4, 5]);

// 测试长度
assert_eq!(arr.len(), 5);

// 测试有效索引
assert_eq!(arr.get(0), Some(&1));
assert_eq!(arr.get(4), Some(&5));

// 测试无效索引
assert_eq!(arr.get(5), None);
}

#[test]
fn test_different_types() {
let arr = FixedArray::new(["hello", "world"]);
assert_eq!(arr.len(), 2);
assert_eq!(arr.get(0), Some(&"hello"));
assert_eq!(arr.get(1), Some(&"world"));
}

#[test]
fn test_empty_array() {
let arr: FixedArray<i32, 0> = FixedArray::new([]);
assert_eq!(arr.len(), 0);
assert_eq!(arr.get(0), None);
}
}
77 changes: 77 additions & 0 deletions exercises/24_new_features/02_let_else.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// let_else.rs
//
// let-else 语句是 Rust 1.65 中引入的新特性。它允许我们在模式匹配失败时
// 提前返回或中断执行。这个特性特别适合于处理 Option 和 Result 类型。
//
// 在这个练习中,我们将使用 let-else 语句来简化错误处理代码。

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

#[derive(Debug, PartialEq)]
struct Rectangle {
top_left: Point,
bottom_right: Point,
}

impl Rectangle {
// TODO: 使用 let-else 语句实现这个函数
// 如果参数无效(左上角点的坐标大于右下角点的坐标),返回 None
fn new(top_left: Point, bottom_right: Point) -> Option<Rectangle> {
todo!("实现 Rectangle::new,使用 let-else 语句验证参数")
}

// TODO: 使用 let-else 语句实现这个函数
// 函数应该解析字符串格式的矩形定义,格式为 "x1,y1,x2,y2"
// 其中 x1,y1 是左上角坐标,x2,y2 是右下角坐标
fn parse(s: &str) -> Option<Rectangle> {
todo!("实现字符串解析为 Rectangle 的功能")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_valid_rectangle() {
let rect = Rectangle::new(
Point { x: 0, y: 0 },
Point { x: 10, y: 10 }
);
assert!(rect.is_some());
}

#[test]
fn test_invalid_rectangle() {
let rect = Rectangle::new(
Point { x: 10, y: 10 },
Point { x: 0, y: 0 }
);
assert!(rect.is_none());
}

#[test]
fn test_parse_valid() {
let rect = Rectangle::parse("0,0,10,10");
assert_eq!(rect, Some(Rectangle {
top_left: Point { x: 0, y: 0 },
bottom_right: Point { x: 10, y: 10 }
}));
}

#[test]
fn test_parse_invalid_format() {
assert!(Rectangle::parse("invalid").is_none());
assert!(Rectangle::parse("0,0,10").is_none());
assert!(Rectangle::parse("0,0,10,10,12").is_none());
}

#[test]
fn test_parse_invalid_coordinates() {
assert!(Rectangle::parse("10,10,0,0").is_none());
}
}
77 changes: 77 additions & 0 deletions exercises/24_new_features/03_gat.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// gat.rs
//
// GAT (Generic Associated Types) 是 Rust 1.65 中引入的一个强大特性。
// 它允许在关联类型中使用泛型参数,这在创建容器类型和迭代器时特别有用。
//
// 在这个练习中,我们将实现一个简单的 Map 容器,它可以存储不同类型的值
// 并提供类型安全的访问方法。

#![allow(dead_code)]

// 定义一个特征,表示可以存储和检索值的容器
trait Container {
// TODO: 使用 GAT 定义一个关联类型 Value,它有一个生命周期参数
type Value<'a>: 'a
where
Self: 'a;

// 获取容器中的值的引用
fn get<'a>(&'a self) -> Self::Value<'a>;
}

// 一个简单的包装类型
struct Wrapper<T>(T);

// TODO: 为 Wrapper<T> 实现 Container 特征
// Value 类型应该是对 T 的引用
impl<T> Container for Wrapper<T> {
type Value<'a> = todo!("定义正确的关联类型");

fn get<'a>(&'a self) -> Self::Value<'a> {
todo!("返回对内部值的引用")
}
}

// 一个选项包装类型
struct OptionWrapper<T>(Option<T>);

// TODO: 为 OptionWrapper<T> 实现 Container 特征
// Value 类型应该是 Option<&T>
impl<T> Container for OptionWrapper<T> {
type Value<'a> = todo!("定义正确的关联类型");

fn get<'a>(&'a self) -> Self::Value<'a> {
todo!("返回 Option<&T>")
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_wrapper() {
let w = Wrapper(42);
assert_eq!(*w.get(), 42);
}

#[test]
fn test_option_wrapper_some() {
let w = OptionWrapper(Some(42));
assert_eq!(w.get(), Some(&42));
}

#[test]
fn test_option_wrapper_none() {
let w: OptionWrapper<i32> = OptionWrapper(None);
assert_eq!(w.get(), None);
}

// 这个测试确保我们的实现可以处理不同的生命周期
#[test]
fn test_lifetime() {
let w = Wrapper(String::from("hello"));
let r: &String = w.get();
assert_eq!(r, "hello");
}
}
103 changes: 103 additions & 0 deletions exercises/24_new_features/04_async_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// async_trait.rs
//
// 异步特征是 Rust 1.75 中稳定的新特性。它允许在特征中直接定义异步方法,
// 不再需要使用 async-trait 宏。这个特性大大简化了异步编程的代码。
//
// 在这个练习中,我们将实现一个简单的异步数据获取接口。

#![allow(dead_code)]

use std::time::Duration;

// 模拟一个数据源
struct DataSource {
data: Vec<String>,
}

impl DataSource {
fn new() -> Self {
Self {
data: vec![
"Hello".to_string(),
"World".to_string(),
"Rust".to_string(),
],
}
}
}

// TODO: 实现一个异步特征 AsyncDataFetcher
// 它应该包含以下异步方法:
// - fetch_data: 获取指定索引的数据
// - fetch_all: 获取所有数据
// - count: 获取数据总数
trait AsyncDataFetcher {
async fn fetch_data(&self, index: usize) -> Option<String>;
async fn fetch_all(&self) -> Vec<String>;
async fn count(&self) -> usize;
}

// TODO: 为 DataSource 实现 AsyncDataFetcher 特征
impl AsyncDataFetcher for DataSource {
async fn fetch_data(&self, index: usize) -> Option<String> {
todo!("模拟异步获取数据,使用 tokio::time::sleep 增加延迟")
}

async fn fetch_all(&self) -> Vec<String> {
todo!("模拟异步获取所有数据")
}

async fn count(&self) -> usize {
todo!("模拟异步获取数据数量")
}
}

#[cfg(test)]
mod tests {
use super::*;
use tokio::time::sleep;

#[tokio::test]
async fn test_fetch_data() {
let source = DataSource::new();

// 测试获取有效索引的数据
assert_eq!(source.fetch_data(0).await, Some("Hello".to_string()));
assert_eq!(source.fetch_data(1).await, Some("World".to_string()));

// 测试获取无效索引的数据
assert_eq!(source.fetch_data(10).await, None);
}

#[tokio::test]
async fn test_fetch_all() {
let source = DataSource::new();
let all_data = source.fetch_all().await;

assert_eq!(all_data, vec![
"Hello".to_string(),
"World".to_string(),
"Rust".to_string(),
]);
}

#[tokio::test]
async fn test_count() {
let source = DataSource::new();
assert_eq!(source.count().await, 3);
}

#[tokio::test]
async fn test_concurrent_fetch() {
let source = DataSource::new();

// 测试并发获取数据
let (data1, data2) = tokio::join!(
source.fetch_data(0),
source.fetch_data(1)
);

assert_eq!(data1, Some("Hello".to_string()));
assert_eq!(data2, Some("World".to_string()));
}
}
16 changes: 16 additions & 0 deletions exercises/24_new_features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Rust 新特性练习

这个章节包含了一些 Rust 最新特性的练习。通过这些练习,你将学习到:

1. const 泛型(Rust 1.51+)
2. GAT (Generic Associated Types) (Rust 1.65+)
3. let-else 语句(Rust 1.65+)
4. 异步特征(Rust 1.75+)

## 推荐阅读

* [Rust 1.51 发布说明](https://blog.rust-lang.org/2021/03/25/Rust-1.51.0.html)
* [Rust 1.65 发布说明](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html)
* [Rust 1.75 发布说明](https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html)
* [Rust Reference: Generic Associated Types](https://doc.rust-lang.org/reference/items/associated-items.html#generic-associated-types-gats)
* [Rust Reference: const 泛型](https://doc.rust-lang.org/reference/items/generics.html#const-generics)

Check failure on line 16 in exercises/24_new_features/README.md

View workflow job for this annotation

GitHub Actions / fmt

Files should end with a single newline character

exercises/24_new_features/README.md:16:101 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md047.md

Check failure on line 16 in exercises/24_new_features/README.md

View workflow job for this annotation

GitHub Actions / fmt

Trailing spaces

exercises/24_new_features/README.md:16:101 MD009/no-trailing-spaces Trailing spaces [Expected: 0 or 2; Actual: 1] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md009.md
Loading