|
refer to some value without taking ownership of it Here is how you would define and use a calculate_length function that has a reference to an object as a parameter instead of taking ownership of the value:
[root@itoracle test]# cargo new references
Created binary (application) `references` package
[root@itoracle test]# cd references/
[root@itoracle references]# vim src/main.rs
fn main() {
let s1 = String::from("wa ka ka ");
let _len = get_length(&s1);
println!("The length of '{}' is {}",s1,_len);
}
fn get_length(ss: &String) -> usize{
ss.len()
}
[root@itoracle references]# cargo run
Compiling references v0.1.0 (/usr/local/automng/src/rust/test/references)
Finished dev [unoptimized + debuginfo] target(s) in 7.50s
Running `target/debug/references`
The length of 'wa ka ka ' is 9
These ampersands are references, and they allow you to refer to some value without taking ownership of it.
The &s1 syntax lets us create a reference that refers to the value of s1 but does not own it. Because it does not own it, the value it points to will not be dropped when the reference goes out of scope. Likewise, the signature of the function uses & to indicate that the type of the parameter s is a reference. Let’s add some explanatory annotations:
fn get_length(s: &String) -> usize { // s is a reference to a String
s.len()
} // Here, s goes out of scope. But because it does not have ownership of what
// it refers to, nothing happens.
如果使用ownership方式求字符串长度,s1的可用范围就立即变化,引用的方式不会使变量可用范围发生变化, 以下是ownership的实现方式
fn main() {
let s1 = String::from("wa ka ka ");
let _len = get_length(s1); //基于ownership 时s1 move到了方法中,作用域虽然没有变,但后续不能再使用s1变量了
// println!("The length of '{}' is {}",s1,_len);
println!("The length is {}",_len);
}
fn get_length(ss: String) -> usize{
ss.len()
}
|
|
Mutable References
fn main() {
let mut s1 = String::from("wa ka ka ");
change(&mut s1);
println!("s1={}",s1);
}
fn change(my_str: &mut String){
my_str.push_str("!!!");
}
[root@itoracle references]# cargo run
s1=wa ka ka !!!
only one mutable reference to a particular piece of data in a particular scope First, we had to change s to be mut. Then we had to create a mutable reference with &mut s and accept a mutable reference with some_string: &mut String. But mutable references have one big restriction: you can have only one mutable reference to a particular piece of data in a particular scope. This code will fail: 下面代码是错误的
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
println!("{}, {}", r1, r2);
This restriction allows for mutation but in a very controlled fashion. It’s something that new Rustaceans struggle with, because most languages let you mutate whenever you’d like.
The benefit of having this restriction is that Rust can prevent data races at compile time. A data raceis similar to a race condition and happens when these three behaviors occur:
Two or more pointers access the same data at the same time.
At least one of the pointers is being used to write to the data.
There’s no mechanism being used to synchronize access to the data.
Data races cause undefined behavior and can be difficult to diagnose and fix when you’re trying to track them down at runtime; Rust prevents this problem from happening because it won’t even compile code with data races! As always, we can use curly brackets to create a new scope, allowing for multiple mutable references, just not simultaneous ones:
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let r2 = &mut s;
下一次的可变引用,要建立在上一次的引用“不可用”的基础的上,下面的做法也可以 这里的 不可用,是指作用域可能还没消失,但运行时不可调用了,代码中不可使用了
fn main() {
let mut s1 = String::from("wa ka ka ");
change(&mut s1);
println!("s1={}",s1);
{
let _r1 = &mut s1;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let _r2 = &mut s1;
change(&mut s1); //_r2在可变s1被第二次引用时不可再使用,后面不能再对_r2使用了
}
fn change(my_str: &mut String){
my_str.push_str("!!!");
}
对于可变引用,如果代码中有一个以上的地方引用,那就表示至少有两处地方可以对该变量修改, 不符合rust可变变量只能有一处地方可修改的规则,编辑阶段就会报错。 一个可变变量同一作用域中只能有一处可用引用,新的引用生效时,已有引用失效,后续无法再次使用。 将已经用过一次的可变引用以方法参数传递时,之前对该可变变量的引用自动失效,只要后续不再使用之前的引用变量,代码可以正常运行; 如果是直接将可变引用第二次赋值到新变量,则是编译阶段报错。
fn main() {
let mut s1 = String::from("wa ka ka ");
change(&mut s1);
println!("s1={}",s1);
{
let _r1 = &mut s1;
} // r1 goes out of scope here, so we can make a new reference with no problems.
let _r2 = &mut s1;
change(&mut s1);
let r3 = &s1; // no problem
let r4 = &s1; // no problem
let r5 = &mut s1; //前面还有引用(_r2)未失效,这里会报错
println!("{},{}",r3,r4);
}
fn change(my_str: &mut String){
my_str.push_str("!!!");
}
error[E0502]: cannot borrow `s1` as mutable because it is also borrowed as immutable
--> src/main.rs:14:14
|
12 | let r3 = &s1; // no problem
| --- immutable borrow occurs here
13 | let r4 = &s1; // no problem
14 | let r5 = &mut s1;
| ^^^^^^^ mutable borrow occurs here
15 | println!("{},{}",r3,r4);
| -- immutable borrow later used here
error: aborting due to previous error
For more information about this error, try `rustc --explain E0502`.
error: Could not compile `references`.
|
字符串操作
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
Because we need to go through the String element by element and check whether a value is a space,
we’ll convert our String to an array of bytes using the as_bytes method
For now, know that iter is a method that returns each element in a collection and that enumerate wraps the result of iter and returns each element as part of a tuple instead. The first element of the tuple returned from enumerate is the index, and the second element is a reference to the element. This is a bit more convenient than calculating the index ourselves. Because the enumerate method returns a tuple, we can use patterns to destructure that tuple, just like everywhere else in Rust. So in the for loop, we specify a pattern that has i for the index in the tuple and &item for the single byte in the tuple. Because we get a reference to the element from .iter().enumerate(), we use & in the pattern. Inside the for loop, we search for the byte that represents the space by using the byte literal syntax. If we find a space, we return the position. Otherwise, we return the length of the string by using s.len()
下面的代码语法上是没有问题,clear之后_word仍是5,这只是不同处理阶段的结果
fn main() {
let mut s = String::from("hello world");
let _word = first_word(&s); // word will get the value 5
println!("{},{}",s,_word);
s.clear(); // this empties the String, making it equal to ""
// word still has the value 5 here, but there's no more string that
// we could meaningfully use the value 5 with. word is now totally invalid!
}
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
下面的代码是正确的,只需要更换一下对切片使用的位置
pub fn test_s2(){
let mut s = String::from("hello world");
let word = first_word2(&s);
println!("the first word is: {}", word);
s.clear(); // right!
}
//传入原字符的引用,原字符没有ownership,返回原字符的切片
fn first_word2(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[..i];
}
}
&s[..]
}
```
pub fn plus_one_test() {
println!("{}","-----vec 修改------------------");
let mut v1 = vec![1, 3, 2];
plus_one(&mut v1);
for (i,val) in v1.iter().enumerate() {
println!("{}:{}",i,val);
}
}
pub fn test_for1(){
let aa = [1,2,3,4,5];
for (index,&bb) in aa.iter().enumerate() {
println!("{}:{}",index,bb);
}
}
pub fn test_for2(){
let aa = [1,2,3,4,5];
for (index,bb) in aa.iter().enumerate() {
println!("{}:{}",index,bb);
}
}
```
|
- for (i,val) in v1.iter().enumerate()
- v1.iter() 返回的迭代器直接产生值的引用 &i32
- val 的类型是 &i32,所以 println! 会自动解引用
- 数组的情况 (g_vec.rs:141):
- for (index,&bb) in aa.iter().enumerate()
- aa.iter() 返回的迭代器也产生值的引用 &i32
- 使用 &bb 模式匹配,将 &i32 解构为 i32,所以 bb 的类型是 i32
- 为什么数组可以用 &bb:
- &bb 是一个模式匹配,用来解引用迭代器产生的引用
- 这样 bb 直接是 i32 类型,而不是 &i32
- 两种方式都可以,但推荐保持一致:
- 要么都用 for (_, val) (保持引用)
- 要么都用 for (_, &val) (解引用)
|
对于 Rust 的 i32 类型,i32 和 &i32 的主要区别在于:
1. 所有权和内存
i32 - 拥有值
let x: i32 = 42; // x 拥有值 42,在栈上
&i32 - 引用值
let x: i32 = 42;
let y: &i32 = & x; // y 是指向 x 的引用,不拥有值
2. 大小
assert_eq!(std::mem::size_of::< i32 > (), 4); // 4 字节
assert_eq!(std::mem::size_of::< &i32 > (), 8); // 8 字节 (64位系统上)
3. 使用方式
let x: i32 = 42;
let y: &i32 = & x;
// 直接使用 i32
println!("{}", x); // 输出: 42
println!("{}", x + 1); // 可以直接运算
// 使用 &i32 需要解引用
println!("{}", *y); // 输出: 42 (需要 *)
println!("{}", *y + 1); // 需要先解引用
4. 在循环中的应用
回到你之前的代码例子:
// Vec 迭代 - val 是 &i32
for (i, val) in v1.iter().enumerate() {
println!("{}:{}", i, val); // println! 自动解引用
println!("{}:{}", i, *val); // 手动解引用
// println!("{}:{}", i, val + 1); // 错误!不能直接对引用运算
}
// 数组迭代 - &bb 解构后 bb 是 i32
for (index, &bb) in aa.iter().enumerate() {
println!("{}:{}", index, bb); // bb 已经是 i32
println!("{}:{}", index, bb + 1); // 可以直接运算
}
5. 函数参数
fn process_val(val: i32) {
println!("处理值: {}", val);
}
fn process_ref(val: &i32) {
println!("处理引用: {}", *val);
}
let x = 42;
process_val(x); // 传递值
process_ref(&x); // 传递引用
总结:i32 是实际的值,而 &i32 是指向值的引用。选择使用哪种取决于是否需要转移所有权。
|
|
|
|
|
|
下面的代码是正确的,使用 `unwrap()` 等待线程结束:
```rust
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread", i);
thread::sleep(Duration::from_millis(1));
}
handle.join().unwrap(); // 等待线程结束
}
```
|
|
下面的代码是错误的,把 `unwrap()` 换成了 `?` 运算符:
```rust
use std::thread;
use std::time::Duration;
fn main() {
let handle = thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread", i);
thread::sleep(Duration::from_millis(1));
}
handle.join()?; // 等待线程结束
}
```
编译报错:
```text
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:17:18
|
4 | fn main() {
| --------- this function should return `Result` or `Option` to accept `?`
...
17 | handle.join()?; // 等待线程结束
| ^ cannot use the `?` operator in a function that returns `()`
|
help: consider adding return type
```
|
|
### 原因
是的,理解正确。`?` 运算符的作用是:当表达式结果为 `Err` 时,**把这个错误作为当前函数的返回值直接返回**,由调用者继续处理。因此它只能用在**返回类型为 `Result` 或 `Option`**(或实现了 `FromResidual` 的类型)的函数中。
而这里 `main` 函数没有写返回类型,默认返回 `()`。`()` 无法承接 `Err` 值,所以编译器报错:不能在返回 `()` 的函数中使用 `?`。
`handle.join()` 的返回类型是 `Result<(), Box<dyn Any + Send>>`(子线程 panic 时返回 `Err`),所以:
- `unwrap()`:成功时取出 `Ok` 里的值,失败时直接 panic —— 对返回类型没有要求,所以正确
- `?`:失败时把 `Err` 向上传播 —— 要求当前函数也返回 `Result`/`Option`,所以报错
### 解决方法一:继续使用 unwrap()
```rust
handle.join().unwrap(); // 出错时 panic,适合 main 或测试,已实测通过
```
### 解决方法二:让 main 返回 Result(有坑)
直觉的写法是给 main 加上 `Result` 返回类型:
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
...
handle.join()?;
Ok(())
}
```
**注意:这个写法对 `join()` 仍然编译不过**,会产生第二层的类型转换错误,详见下一个 Tab「二次报错:错误类型转换」。
|
|
### 二次报错
把 main 改成返回 `Result<(), Box<dyn std::error::Error>>` 后,`handle.join()?` 报新错误:
```text
error[E0277]: `?` couldn't convert the error: `dyn Any + Send: std::error::Error` is not satisfied
--> src/main.rs:17:18
|
4 | fn main() -> Result<(), Box<dyn std::error::Error>> {
| -------------------------------------- required `dyn Any + Send: std::error::Error` because of this
...
17 | handle.join()?;
| ------^ the trait `std::error::Error` is not implemented for `dyn Any + Send`
| |
| this can't be annotated with `?` because it has type `Result<_, Box<(dyn Any + Send + 'static)>>`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= note: required for `Box<dyn Any + Send>` to implement `std::error::Error`
= note: required for `Box<dyn std::error::Error>` to implement `From<Box<dyn Any + Send>>`
```
### 深层原因
- `handle.join()` 的返回类型是 `Result<(), Box<dyn Any + Send + 'static>>`
- 子线程 panic 时携带的 payload 可以是**任意类型**(通常是 `&str` 或 `String`),所以错误类型是 `Box<dyn Any + Send>`,而不是 `Box<dyn Error>`
- `dyn Any + Send` 没有实现 `std::error::Error`,`?` 隐式执行的 `From` 错误转换无法进行,因此报错
- 结论:`?` 不仅要求函数返回 `Result`/`Option`,还要求**两边的错误类型能通过 `From` 相互转换**
### 正确改法一:main 的错误类型与 join() 对齐(已实测通过)
```rust
fn main() -> Result<(), Box<dyn std::any::Any + Send>> {
...
handle.join()?; // 错误类型完全一致,无需转换
Ok(())
}
```
`main` 可以返回 `Result<(), E>`,只要求 `E` 实现了 `Debug`(`Termination` trait 的约束),`Box<dyn Any + Send>` 满足条件。
### 正确改法二:map_err 手动转换错误类型(已实测通过)
```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
...
handle.join().map_err(|e| -> Box<dyn std::error::Error> {
format!("spawned thread panicked: {:?}", e).into()
})?;
Ok(())
}
```
把 panic payload 格式化成 `String` 再装入 `Box<dyn Error>`,`String` 实现了 `Error` 所需的转换。
### 正确改法三:unwrap(最简单,已实测通过)
```rust
fn main() {
...
handle.join().unwrap();
}
```
### 实测总结
在 Linux(cargo)环境对以上三种改法逐一 `cargo run` 验证,均能正常编译并输出主线程与子线程交替打印的结果。
|
|
### 问题:Box<dyn Any + Send> 能装一切类型,能否替代 Box<dyn Error> 作为通用错误返回类型?
结论:**能"装下"一切,但不能"替代"**。
### 1. 存储能力上:是超集 ✅
`Box<dyn Any + Send>` 只要求内容是 `Any + Send + 'static`,任何具体类型都能 `Box::new(x)` 装进去——包括根本不是错误的类型(比如一个 `i32`)。而 `Box<dyn Error>` 只能装实现了 `Error` trait 的类型。
### 2. 但作为通用错误返回类型,三个能力全缺 ❌
**① `?` 无法自动转换(实测报错)**
```rust
fn main() -> Result<(), Box<dyn Any + Send>> {
let _s = std::fs::read_to_string("/nonexistent")?; // ❌ E0277
Ok(())
}
```
`Box<dyn Error>` 有标准库的**泛型实现** `impl<E: Error> From<E> for Box<dyn Error>`,任何具体错误(`io::Error`、`ParseIntError`……)用 `?` 都能自动装箱。而 `Box<dyn Any + Send>` **没有任何这样的 `From` 实现**(实测 `String`/`&str` 的 `.into()` 也都不行——那两个 `From` 实现是给 `Box<dyn Error + Send + Sync>` 的)。它之前能配 `?` 用,纯粹因为 `join()` 的错误类型和它**一字不差**,走的是恒等转换,不是泛型转换。
**② 没有错误行为**
`dyn Error` 自带 `Display`(能直接打印可读信息)和 `source()`(能遍历错误链)。`dyn Any` 只有类型识别能力——拿到后必须 `downcast_ref::<具体类型>()` 逐个猜,猜不中连错误内容都打印不出来。
**③ 丢失语义约定**
返回值写 `Box<dyn Error>` 是在告诉调用者"这是错误";写 `Box<dyn Any + Send>` 则什么承诺都没有,生态里的 `anyhow`、`thiserror`、`eyre` 全都围绕 `Error` trait 构建,接不上。
### 3. 它的正确定位:panic payload 专用类型
`Box<dyn Any + Send + 'static>` 是 **panic payload 的专用类型**——`thread::join()`、`catch_unwind()` 用它,因为 panic 可以携带任意类型(`panic!("{}", 42)` 都行)。典型用法是接住后 downcast 还原:
```rust
if let Err(e) = handle.join() {
if let Some(s) = e.downcast_ref::<&str>() {
eprintln!("panicked with: {}", s);
} else if let Some(s) = e.downcast_ref::<String>() {
eprintln!("panicked with: {}", s);
}
}
```
### 4. 对比总结
| | `Box<dyn Error>` | `Box<dyn Any + Send>` |
|---|---|---|
| 能装的类型 | 仅 `Error` 实现者 | 一切 `Send + 'static` 类型 |
| `?` 自动转换 | ✅ 泛型 `From<E: Error>` | ❌ 几乎没有 `From` 实现 |
| `Display` / `source()` 错误链 | ✅ | ❌ 只能 downcast |
| 用途 | 通用错误返回 | panic payload 传递 |
所以通用错误类型还是 `Box<dyn Error>`(或 `anyhow::Error`),`Box<dyn Any + Send>` 只在接 panic 的场景出现。
|