栈上分配固定大小,性能高。堆中分配随意大小,但耗费性能。
引用与借用的区别,什么情况下会发生栈上复制?不会产生借用。
字符串字面量和基本类型都是复制,指针也是。
深拷贝Clone与浅拷贝Copy,
什么类型可以浅拷贝Copy?
函数入参数和返回值会发生转移。
编译器对作用域的优化
悬垂引用
默认情况下,非 Copy 类型的赋值和函数传递会导致所有权转移。
实现了 Copy trait 的类型赋值和函数传递会进行值的复制。
是的,你的理解是正确的。以下是具体的类型和它们的行为总结:
### 实现了 `Copy` Trait 的类型
1. **所有整数类型**:
- 例如:`u8`, `u16`, `u32`, `u64`, `u128`, `i8`, `i16`, `i32`, `i64`, `i128`
2. **布尔类型**:
- `bool`,它的值是 `true` 和 `false`
3. **所有浮点数类型**:
- 例如:`f32`, `f64`
4. **字符类型**:
- `char`
5. **元组**:
- 元组类型当且仅当其包含的所有类型也都是 `Copy` 类型时,它才是 `Copy` 的。例如:
- `(i32, i32)` 是 `Copy` 的,因为 `i32` 是 `Copy` 的。
- `(i32, String)` 不是 `Copy` 的,因为 `String` 不是 `Copy` 的。
6. **不可变引用**:
- 不可变引用 `&T` 是 `Copy` 的,因为引用本身是一个指针,它的复制是浅拷贝。例如:
```rust
let x = 5;
let y = &x;
let z = y; // y 和 z 都是 &i32 类型的不可变引用
```
### 不实现 `Copy` Trait 的类型
1. **可变引用**:
- 可变引用 `&mut T` 不是 `Copy` 的,因为可变引用的复制会导致数据竞争问题。例如:
```rust
let mut x = 5;
let y = &mut x;
// let z = y; // 这行代码会报错,因为 &mut T 不是 `Copy` 的
```
2. **动态分配的类型**:
- 例如:`String`, `Vec<T>`, `Box<T>` 等,它们在堆上分配内存,默认情况下不实现 `Copy`。
### 示例代码说明
以下是一些示例代码,展示了不同类型的赋值和所有权转移行为:
```rust
fn main() {
// 整数类型
let a = 10;
let b = a; // 直接复制,a 仍然有效
println!("a = {}, b = {}", a, b);
// 布尔类型
let c = true;
let d = c; // 直接复制,c 仍然有效
println!("c = {}, d = {}", c, d);
// 浮点数类型
let e = 3.14;
let f = e; // 直接复制,e 仍然有效
println!("e = {}, f = {}", e, f);
// 字符类型
let g = 'a';
let h = g; // 直接复制,g 仍然有效
println!("g = {}, h = {}", g, h);
// 元组类型
let i = (10, 20);
let j = i; // 直接复制,i 仍然有效
println!("i = ({}, {}), j = ({}, {})", i.0, i.1, j.0, j.1);
// 不可变引用
let k = 42;
let l = &k; // 不可变引用,直接复制,k 仍然有效
let m = l; // l 和 m 都是 &i32 类型的不可变引用
println!("k = {}, l = {}, m = {}", k, l, m);
// 可变引用
let mut n = 100;
let o = &mut n;
// let p = o; // 这行代码会报错,因为 &mut T 不是 `Copy` 的
println!("o = {}", o);
}
```
### 总结
- **实现了 `Copy` Trait 的类型**:赋值操作会进行值的复制,原变量仍然有效。
- **不实现 `Copy` Trait 的类型**:赋值操作会导致所有权转移,原变量不再有效。
- **不可变引用**:是 `Copy` 的,可以安全地复制。
- **可变引用**:不是 `Copy` 的,复制会导致数据竞争问题。
&str 是一个不可变引用类型,而不可变引用类型是实现了 Copy trait 的。因此,&str 类型的变量是可以被复制的。
String与&str
fn main() {
let s = String::from("hello,world!");
say_hello(&s);
say_hello(&s[..]);
say_hello(s.as_str());
}
fn say_hello(s: &str) {
println!("{}",s);
}
String::from("hello,world")
"hello,world".to_string()
必须要将结构体实例声明为可变的,才能修改其中的字段,Rust 不支持将某个结构体某个字段标记为可变。
当函数参数和结构体字段同名时,可以直接使用缩略的方式进行初始化,跟 TypeScript 中一模一样。
let user2 = User {
email: String::from("another@example.com"),
..user1
};
结构体赋值可能会导致所有权转移。
元组结构体在你希望有一个整体名称,但是又不关心里面字段的名称时将非常有用。
dbg!(&rect1); 输出控制台信息,包含文件名、行号、表达式以及表达式的值。
通过 :: 操作符来访问 枚举 下的具体成员。
如果想在循环中,修改该元素,可以使用 mut 关键字:
for item in &mut collection {
// ...
}
同时获取索引
let a = [4, 3, 2, 1];
// `.iter()` 方法把 `a` 数组变成一个迭代器
for (i, v) in a.iter().enumerate() {
println!("第{}个元素是{}", i + 1, v);
}
当使用 loop 时,必不可少的伙伴是 break 关键字,它能让循环在满足某个条件时跳出:
fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2;
}
};
println!("The result is {}", result);
}
match还可以用 |
enum Direction {
East,
West,
North,
South,
}
fn main() {
let dire = Direction::South;
match dire {
Direction::East => println!("East"),
Direction::North | Direction::South => {
println!("South or North");
},
_ => println!("West"),
};
}
以及
v.iter().filter(|x| matches!(x, MyEnum::Foo));
if let 往往用于匹配一个模式,而忽略剩下的所有模式的场景
一个与 if let 类似的结构是 while let 条件循环,它允许只要模式匹配就一直进行 while 循环。
枚举内可以不同结构:
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => {
println!("The Quit variant has no data to destructure.")
}
Message::Move { x, y } => {
println!(
"Move in the x direction {} and in the y direction {}",
x,
y
);
}
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => {
println!(
"Change the color to red {}, green {}, and blue {}",
r,
g,
b
)
}
}
}
let ((feet, inches), Point {x, y}) = ((3, 10), Point { x: 3, y: -10 });
println!("feet: {}", feet);
println!("inches: {}", inches);
println!("x: {}", x);
println!("y: {}", y);
同时匹配:
let mut setting_value = Some(5);
let new_setting_value = Some(10);
match (setting_value, new_setting_value) {
(Some(_), Some(_)) => {
println!("Can't overwrite an existing customized value");
}
_ => {
setting_value = new_setting_value;
}
}
println!("setting is {:?}", setting_value);
...省略
struct Point {
x: i32,
y: i32,
z: i32,
}
let origin = Point { x: 0, y: 0, z: 0 };
match origin {
Point { x, .. } => println!("x is {}", x),
}
匹配守卫
match num {
Some(x) if x < 5 => println!("less than five: {}", x),
Some(x) => println!("{}", x),
None => (),
}
这特么也行:这个匹配条件表明此分支只匹配 x 值为 4、5 或 6 同时 y 为 true 的情况。
let x = 4;
let y = false;
match x {
4 | 5 | 6 if y => println!("yes"),
_ => println!("no"),
}
使用 @ 还可以在绑定新变量的同时,对目标进行解构:
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found an id in range: {}", id_variable)
},
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
},
Message::Hello { id } => {
println!("Found some other id: {}", id)
},
}
绑定新#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
// 绑定新变量 `p`,同时对 `Point` 进行解构
let p @ Point {x: px, y: py } = Point {x: 10, y: 23};
println!("x: {}, y: {}", px, py);
println!("{:?}", p);
let point = Point {x: 10, y: 5};
if let p @ Point {x: 10, y} = point {
println!("x is 10 and y is {} in {:?}", y, p);
} else {
println!("x was not 10 :(");
}
}
定义在 impl 中且没有 self 的函数被称之为关联函数。
:: 语法用于关联函数和模块创建的命名空间。
fn display_array<T: std::fmt::Debug, const N: usize>(arr: [T; N]) {
println!("{:?}", arr);
}
fn main() {
let arr: [i32; 3] = [1, 2, 3];
display_array(arr);
let arr: [i32; 2] = [1, 2];
display_array(arr);
}
特征作为函数参数
pub fn notify(item: &impl Summary) {
println!("Breaking news! {}", item.summarize());
}
在简单的场景下 impl Trait 这种语法糖就足够使用,但是对于复杂的场景,特征约束可以让我们拥有更大的灵活性和语法表现能力,
如果函数两个参数是不同的类型,那么上面的方法很好,只要这两个类型都实现了 Summary 特征即可。但是如果我们想要强制函数的两个参数是同一类型呢?上面的语法就无法做到这种限制,此时我们只能使特征约束来实现:
pub fn notify(item1: &impl Summary, item2: &impl Summary) {}
泛型类型 T 说明了 item1 和 item2 必须拥有同样的类型,同时 T: Summary 说明了 T 必须实现 Summary 特征。
可以通过 impl Trait 来说明一个函数返回了一个类型,该类型实现了某个特征:
fn returns_summarizable() -> impl Summary {
Weibo {
username: String::from("sunface"),
content: String::from(
"m1 max太厉害了,电脑再也不会卡",
)
}
}
在本书中,形如 #[derive(Debug)] 的代码已经出现了很多次,这种是一种特征派生语法,被 derive 标记的对象会自动实现对应的默认特征代码,继承相应的功能。
在一些场景中,使用 as 关键字做类型转换会有比较大的限制,因为你想要在类型转换上拥有完全的控制,例如处理转换错误,那么你将需要 TryInto:
use std::convert::TryInto;
fn main() {
let a: i32 = 10;
let b: u16 = 100;
let b_ = b.try_into()
.unwrap();
if a < b_ {
println!("Ten is less than one hundred.");
}
}
在 Rust 中,`dyn Draw`、`impl Draw` 和 `: Draw` 用于不同的场景,主要涉及到动态分发和静态分发的选择,以及泛型约束。让我们详细讨论一下它们的使用场景。
### 1. `dyn Draw`
`dyn Draw` 用于动态分发,也就是运行时多态。它通常用于需要在运行时确定具体类型的场合,例如在需要存储不同类型但实现了相同 trait 的对象时。
**使用场景**:
- 当你需要在集合中存储不同类型的对象,但这些对象都实现了相同的 trait。
- 当你需要通过 trait 对象来实现动态分发,以便在运行时选择具体的实现。
**示例**:
```rust
trait Draw {
fn draw(&self);
}
struct Circle;
struct Square;
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}
impl Draw for Square {
fn draw(&self) {
println!("Drawing a square");
}
}
fn main() {
let shapes: Vec<Box<dyn Draw>> = vec![Box::new(Circle), Box::new(Square)];
for shape in shapes {
shape.draw();
}
}
```
在这个例子中,`Vec<Box<dyn Draw>>` 可以存储实现了 `Draw` trait 的不同类型的对象,并在运行时调用它们的 `draw` 方法。
### 2. `impl Draw`
`impl Draw` 用于静态分发,也就是编译时多态。它通常用于泛型函数或返回值类型是具体实现了某个 trait 的类型,但不需要在运行时确定具体类型。
**使用场景**:
- 当你编写泛型函数并希望参数或返回值是实现了某个 trait 的具体类型。
- 当你希望通过静态分发来获得更高的性能,因为所有的类型信息在编译时就已经确定。
**示例**:
```rust
trait Draw {
fn draw(&self);
}
struct Circle;
struct Square;
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}
impl Draw for Square {
fn draw(&self) {
println!("Drawing a square");
}
}
fn draw_shape(shape: impl Draw) {
shape.draw();
}
fn main() {
let circle = Circle;
let square = Square;
draw_shape(circle);
draw_shape(square);
}
```
在这个例子中,`draw_shape` 函数接受任何实现了 `Draw` trait 的类型,并在编译时确定具体的类型。
### 3. `: Draw`
`: Draw` 用于泛型约束。当你编写泛型代码时,需要确保泛型类型参数实现了某个 trait。
**使用场景**:
- 当你编写泛型数据结构或函数,并希望泛型参数实现某个 trait。
- 当你希望通过泛型约束来确保类型安全和接口一致性。
**示例**:
```rust
trait Draw {
fn draw(&self);
}
struct Circle;
struct Square;
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}
impl Draw for Square {
fn draw(&self) {
println!("Drawing a square");
}
}
fn draw_shape<T: Draw>(shape: T) {
shape.draw();
}
fn main() {
let circle = Circle;
let square = Square;
draw_shape(circle);
draw_shape(square);
}
```
在这个例子中,`draw_shape` 函数使用泛型参数 `T`,并通过 `T: Draw` 约束确保 `T` 实现了 `Draw` trait。
### 总结
- **`dyn Draw`**:用于动态分发,在运行时确定具体类型,常用于需要存储不同类型的对象的场合。
- **`impl Draw`**:用于静态分发,在编译时确定具体类型,常用于泛型函数或返回值类型是具体实现了某个 trait 的类型的场合。
- **`: Draw`**:用于泛型约束,确保泛型类型参数实现了某个 trait,常用于泛型数据结构或函数的场合。
根据具体需求选择合适的方式,可以在性能和灵活性之间取得平衡。