大家好
今天 完成 2024年自动驾驶OS开发训练营-初阶营第四期-导学
Day2用 rustlings 练习 Rust 语言 -Move Semantics
https://doc.rust-lang.org/stable/book/ch04-00-understanding-ownership.html
提交代码时候 提示 没有权限怎么出来
aciton
参考开发环境配置
https://rcore-os.cn/arceos-tutorial-book/ch01-02.html
我的题目
https://github.com/cicvedu/rustlings-semester-4-watchpoints
创建ssh key,用于ssh方式克隆github代码。 在linux环境下,使用ssh-keygen -t rsa -b 4096 -C "你的邮箱"命令,创建ssh key, 下面的选项全部直接敲回车即可。 随后使用 cat ~/.ssh/id_rsa.pub
Primitive Types
Rust has a couple of basic types that are directly implemented into the compiler. In this section, we'll go through the most important ones.
Further information
-
Data Types -
The Slice Type
The Slice Type Slices let you reference a contiguous sequence of elements in a collection rather than the whole collection.
A slice is a kind of reference, so it does not have ownership.
在 Rust 中,切片(slice)是从数组或另一个切片中引用一系列连续元素的视图。要创建一个切片,你需要指定切片的开始和结束位置(不包含结束位置的索引)。在数组 a
中,如果你想得到 [2, 3, 4]
这个切片,你需要从索引 1 开始,到索引 4 结束(不包含索引 4)。
下面是如何修复你的测试代码:
#[test]
fn slice_out_of_array() {
let a = [1, 2, 3, 4, 5];
// 从索引 1 开始,到索引 4 结束的切片
let nice_slice = &a[1..4];
assert_eq!([2, 3, 4], nice_slice);
}
在这个例子中,&a[1..4]
创建了一个从数组 a
中索引 1 开始到索引 3 结束的切片,因为 Rust 的切片语法是左闭右开区间(包含开始,不包含结束)。这样 nice_slice
就包含了数组 a
中的 [2, 3, 4]
。
在命令行输入rustlings watch 开始编辑代码的循环实验
Understanding Ownership
Ownership is Rust’s most unique feature and has deep implications for the rest of the language.
It enables Rust to make memory safety guarantees without needing a garbage collecto
What Is Ownership?
Ownership Rules First, let’s take a look at the ownership rules. Keep these rules in mind as we work through the examples that illustrate them:
-
Each value in Rust has an owner. -
There can only be one owner at a time. -
When the owner goes out of scope, the value will be dropped
代码示例本身是正确的,并且展示了 Rust 中所有权和复制的概念。但是,如果您想要确保代码能够编译并运行,并且想要满足一些可能的改进或更正,这里有一些建议:
-
确保
takes_ownership
和makes_copy
函数在main
函数之前定义,因为 Rust 中的函数需要先声明后使用。 -
添加注释,说明为什么
s
在takes_ownership
调用后不能被使用,而x
在makes_copy
调用后仍然可以被使用。 -
添加
main
函数的返回类型,虽然在简单的程序中这不是必需的,但这是一个好的编程习惯。 -
使用 Rust 的格式化工具,比如
rustfmt
,来保持代码的整洁。
下面是根据上述建议修改后的代码:
fn takes_ownership(some_string: String) { // 函数定义在 main 之前
println!("{}", some_string);
} // some_string 的作用域结束,调用 Drop
fn makes_copy(some_integer: i32) { // 函数定义在 main 之前
println!("{}", some_integer);
} // some_integer 的作用域结束,没有调用 Drop,因为 i32 是 Copy 类型
fn main() -> () { // 明确 main 函数的返回类型为 ()
let s = String::from("hello"); // s 拥有 "hello" 的所有权
takes_ownership(s); // s 的值移动到 takes_ownership 函数
// 下面的代码尝试使用 s 将会导致编译错误,因为 s 的值已经移动
// let len = s.len(); // 错误:s 已移动
let x = 5; // x 包含值 5
makes_copy(x); // x 的值被复制到 makes_copy 函数
// x 仍然有效,因为 i32 是 Copy 类型
// x 仍然可以使用
println!("x is {}", x);
}
在这个修改后的版本中,我们添加了 main
函数的返回类型 ()
,这表示 main
函数不返回任何值。同时,注释被添加来解释为什么 s
在 takes_ownership
调用后不能被使用,而 x
在 makes_copy
调用后仍然有效。此外,我们还展示了尝试在 takes_ownership
调用后使用 s
会导致编译错误的例子,以及 x
在 makes_copy
调用后仍然可以使用的例子。
Vectors
Vectors are one of the most-used Rust data structures.
In other programminglanguages, they'd simply be called Arrays, but since Rust operates on a bit of a lower level, an array in Rust is stored on the stack (meaning it can't grow or shrink, and the size needs to be known at compile time), and a Vector is stored in the heap (where these restrictions do not apply).
Vectors are a bit of a later chapter in the book, but we think that they're useful enough to talk about them a bit earlier. We shall be talking about the other useful data structure, hash maps, later.
Further information
-
Storing Lists of Values with Vectors -
iter_mut
-
map
vec_map
函数的作用是接受一个 Vec<i32>
类型的引用(即一个包含 32 位整数的向量),然后返回一个新的 Vec<i32>
,这个新向量中的每个元素都是原向量中对应元素的两倍。
具体来说,vec_map
函数中的 map
和 collect
方法是这样工作的:
-
**
v.iter()
**:这个方法创建了一个迭代器,它遍历v
向量的每个元素。iter
方法返回的迭代器提供的是向量中元素的不可变引用。 -
**
.map(|element| { element * 2 })
**:map
方法接受一个闭包(匿名函数),在这个例子中,闭包接受一个参数element
(向量中的一个元素的不可变引用),然后返回这个元素值乘以 2 的结果。闭包体内的操作element * 2
实际上是在对引用指向的数据进行解引用(通过*
操作符)并执行乘法操作。 -
**
.collect()
**:map
方法返回的是一个惰性迭代器,它逐个应用闭包中的操作,但不会立即收集结果。collect
方法被调用来将这些经过map
处理的元素收集到一个新的Vec<i32>
向量中。
使用 map
和 collect
的好处是,它们允许你以一种声明性的方式对集合中的每个元素执行操作,并将结果收集到新的集合中,而不需要编写显式的循环。这种方式代码更简洁,也更符合 Rust 的风格。
这里是一个如何使用 vec_map
函数的示例:
fn main() {
let original_vec = vec![1, 2, 3, 4];
let doubled_vec = vec_map(&original_vec);
println!("{:?}", doubled_vec); // 这将打印:[2, 4, 6, 8]
}
在这个示例中,vec_map
接收 original_vec
的引用,创建了一个新的向量 doubled_vec
,其中包含了原向量每个元素的两倍值,并打印出来。
Move Semantics
These exercises are adapted from pnkfelix's Rust Tutorial -- Thank you Felix!!!
Further information
For this section, the book links are especially important.
-
Ownership -
Reference and borrowing
move_semantics1.rs
move_semantics2.rs
// move_semantics5.rs
Rust 中的可变引用有一条规则:在任意给定时间,你可以拥有任意数量的不可变引用,或者至多一个可变引用。这条规则确保了内存安全,防止了数据竞争。
在提供的代码中,存在一个问题:main
函数试图创建两个可变引用 y
和 z
,都指向同一个变量 x
。这是不允许的,因为 Rust 的借用规则不允许在同一作用域内对同一变量有多个可变引用。这就是为什么代码无法编译的原因。
要修复这个问题,你需要确保在任何给定时间,只有一个可变引用存在。这里是修复后的代码:
fn main() {
let mut x = 100;
{
let y = &mut x;
*y += 100; // 此时 x 的值变为 200
} // 'y' 的作用域结束,可变引用被销毁
{
let z = &mut x; // 可以创建新的可变引用,因为 'y' 已经不存在了
*z += 1000; // 此时 x 的值变为 1200
} // 'z' 的作用域结束,可变引用被销毁
assert_eq!(x, 1200); // 断言 x 的值是 1200,这是正确的
}
在这个修复的版本中,我们通过创建内部作用域来确保在任何时候只有一个可变引用存在。首先,我们创建了 y
的作用域,对其进行了操作,然后这个作用域结束,y
不再有效。之后,我们创建了 z
的作用域,再次对 x
进行操作。这样就遵守了 Rust 的借用规则,使得代码可以编译并按预期运行。
这段编译错误信息指出了 Rust 代码中的一个生命周期问题。问题出在 string_uppercase
函数的实现上,它试图将一个临时值赋给一个引用,这是不允许的。
这里是出错的代码行:
data = &data.to_uppercase();
在这行代码中,data.to_uppercase()
创建了一个 String
的新实例,这个实例是一个临时值,它的生命周期依赖于 data
的借用。然而,尝试将这个临时值的引用赋给 data
是不合法的,因为临时值会在语句结束时立即被销毁,而 data
需要一个比临时值更长的生命周期。
要修复这个问题,我们需要修改 string_uppercase
函数,让它接受一个可变引用,并且直接在该引用指向的数据上调用 make_uppercase()
方法,而不是尝试重新赋值。这里是修复后的代码:
fn string_uppercase(data: &mut String) {
data.make_uppercase(); // 直接在 data 上调用 make_uppercase 方法
println!("{}", data);
}
在这个修复版本中,我们移除了 mut data: &String
中的 mut
,因为 data
已经是一个可变引用。然后我们直接在 data
上调用 make_uppercase()
,这样就不会有临时值的问题。make_uppercase()
方法会修改 data
引用指向的 String
实例,而不需要创建新的 String
。
此外,main
函数中的调用也需要相应地更新,以传递一个可变引用:
fn main() {
let mut data = "Rust is great!".to_string();
string_uppercase(&mut data); // 传递一个可变引用
println!("{}", data); // 打印修改后的字符串
}
现在,string_uppercase
函数接受 data
的可变引用,直接修改它,并且 main
函数中的 data
是可变的,可以被 string_uppercase
所修改。这样代码就可以编译并按预期工作了。
/**
【昵称】小王同学
【坐标】山东
【自我介绍】
1. 高级后端工程师,擅长c++并发编程。
2. 专注分布式存储,分布式数据库。
3. 时间的践行者,立志成为一名讲师。
【我的成绩】
1. 为了走出农村,2次高考
一次考研失败,
遇挫之后不知道干什么,开启被动之旅。
2. 为了找到合适工作,
深入研究c++书籍和leetcode 200题目
3. 为了提高项目能力,参与开源项目建设。
4. 为了把简单事情说清楚/要干啥
按照《只管去做》,《福格行为模型>>方法。
纸上得来终觉浅,绝知此事要躬行
做一个践行者。
【我能提供】
1. 后端程序员的简历优化+就业辅导+职业规划
2. 全栈工程师(c++,rust,go,python )项目开发
3. 一年践行12本书践行记录。
【希望一起解决什么,开启破圈之旅】
1. 交接更多朋友,抱团取暖。
寻找意义本身就更加有意义。
2. 无法做整个系统,聚焦一个模块
道可道也,非恒道也
名可名也,非恒名也。
无名 万物之始也
有名 万物之母也
别想太多,只管去做,躬身入局
链接我: # + v(github):watchpoints
#众号:后端开发成长指南
**/
本文由 mdnice 多平台发布