Rust学习产生的疑问罗列与解答

27 次浏览
2024年07月18日创建

范型混合生命周期的理解,以及复杂范型+的demo例子,适用场景。

//TODO

范型和动态分发之间的区别

trait Vegetable {

}

struct Salad<V:Vegetable> {
    veggies:Vec<V>
}

struct Salad {
    veggies: Vec<Box<dyn Vegetable>>
}

第一种`Salad`结构体是泛型的,声明了Vegetable的范型,它可以容纳任何实现了`Vegetable` trait的类型。所有的蔬菜都必须是相同的具体类型,这意味着如果你有一个`Salad<Tomato>`,其中`Tomato`实现了`Vegetable`,那么这个沙拉只能包含番茄,不能包含其他类型的蔬菜。

第二种`Salad`结构体使用了动态分发(dynamic dispatch),这里的`veggies`字段是一个`Box<dyn Vegetable>`类型的向量。`dyn Vegetable`是一个动态trait对象,表示任何实现了`Vegetable` trait的类型的实例。`Box`是一个智能指针,用于在堆上分配内存。使用`Box<dyn Vegetable>`意味着`Salad`可以容纳实现了`Vegetable` trait的任何类型的蔬菜,不需要它们是同一类型。这种方式提供了更高的灵活性,因为你可以将不同类型的蔬菜放在同一个沙拉中。

这个比较好理解吧,一个是作用在class层面的,一个是作用在字段层面的。

为范型扩展特型

//TODO

Self类型用法很多

struct Circle {
    radius: f64,
}

impl Circle {
    // 关联函数,返回一个新的 Circle 实例
    fn new(radius: f64) -> Self {
        Self { radius }
    }

    // 方法,计算圆的面积
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

fn main() {
    let circle = Circle::new(3.0);
    println!("Circle radius: {}", circle.radius);
    println!("Circle area: {}", circle.area());
}

上述例子中充当了java中的this。


enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

impl Shape {
    // 关联函数,创建一个圆形
    fn new_circle(radius: f64) -> Self {
        Self::Circle {radius}
    }

    // 关联函数,创建一个矩形
    fn new_rectangle(width: f64, height: f64) -> Self {
        Self::Rectangle { width, height }
    }

    // 方法,计算形状的面积
    fn area(&self) -> f64 {
        match self {
            Self::Circle { radius } => std::f64::consts::PI * radius * radius,
            Self::Rectangle { width, height } => width * height,
        }
    }
}

#[test]
pub fn self_test(){
    let circle = Shape::new_circle(3.0);
    let rectangle = Shape::new_rectangle(4.0, 5.0);

    println!("Circle area: {}", circle.area());
    println!("Rectangle area: {}", rectangle.area());
}

这里代指本类型的Class,我草。这不就理解了?而Shape本身是枚举,所以通过match匹配枚举是可以的。在一些高级用法中,Self 还可以用于关联类型。假设我们有一个特征 Container,它有一个关联类型 Item:

trait Container {
    type Item;

    fn new(item: Self::Item) -> Self;
    fn get_item(&self) -> &Self::Item;
}

struct Box<T> {
    item: T,
}

impl<T> Container for Box<T> {
    type Item = T;

    fn new(item: Self::Item) -> Self {
        Self { item }
    }

    fn get_item(&self) -> &Self::Item {
        &self.item
    }
}

fn main() {
    let boxed_integer = Box::new(42);
    println!("Box contains: {}", boxed_integer.get_item());
}

在这个示例中,Self 被用来表示实现 Container 特征的类型,并且通过关联类型 Item 来指定容器中存储的项目类型。

通过这些示例,你可以看到 Self 在 Rust 中的多种用法,它使代码更简洁、更具可读性,并且在实现特征和关联函数时非常有用。

如何理解函数签名

### 函数 `dump`

fn dump<I>(iter: I) 
where 
    I: Iterator, 
    I::Item: Debug 
{
    for (index, value) in iter.enumerate() {
        println!("{}{:?}", index, value);
    }
}

#### 解释:

1. **泛型参数 `I`**:

- `fn dump<I>(iter: I)` 声明了一个泛型参数 `I`,表示函数可以接受任何类型,只要该类型满足后面的约束。

2. **`where` 子句**:

- `where I: Iterator` 约束 `I` 必须实现 `Iterator` 特征。

- `I::Item: Debug` 约束 `I` 的 `Item`(即迭代器产生的元素类型)必须实现 `Debug` 特征,以便我们可以在 `println!` 宏中使用 `{:?}` 来打印调试信息。

3. **`for` 循环**:

- `for (index, value) in iter.enumerate()` 使用了迭代器的 `enumerate` 方法,它将每个元素与其索引一起返回。

- `println!("{}{:?}", index, value)` 打印索引和值,其中 `{:?}` 用于调试格式输出值。

### 函数 `dump_string`

fn dump_string<I>(iter: I) 
where 
    I: Iterator<Item = String>, 
    I::Item: Debug 
{
    for (index, value) in iter.enumerate() {
        println!("{}{}", index, value);
    }
}

#### 解释:

1. **泛型参数 `I`**:

- `fn dump_string<I>(iter: I)` 同样声明了一个泛型参数 `I`。

2. **`where` 子句**:

- `where I: Iterator<Item = String>` 约束 `I` 必须是一个迭代器,并且它的 `Item`(迭代器产生的元素类型)必须是 `String`。

- `I::Item: Debug` 约束 `I` 的 `Item` 必须实现 `Debug` 特征。这里实际上是多余的,因为 `String` 已经实现了 `Debug` 特征。

3. **`for` 循环**:

- `for (index, value) in iter.enumerate()` 使用了 `enumerate` 方法。

- `println!("{}{}", index, value)` 打印索引和值,因为值是 `String` 类型,所以直接使用 `{}` 来打印。

### 主要区别

- **泛型约束**:

- `dump` 函数可以接受任何类型的迭代器,只要它的元素类型实现了 `Debug` 特征。

- `dump_string` 函数只能接受元素类型为 `String` 的迭代器。

- **打印方式**:

- `dump` 使用 `{:?}` 来打印元素,因为它不确定元素的具体类型,只要求实现了 `Debug`。

- `dump_string` 直接使用 `{}` 来打印元素,因为它知道元素类型是 `String`。

### 总结

这两个函数展示了 Rust 中泛型和特征约束的强大功能。通过使用泛型参数和 `where` 子句,你可以编写非常灵活和通用的代码,同时确保类型安全。

下面的真复杂哦:

fn dot<N>(v1: &[N], v2: &[N]) -> N where
    N: Add<Output = N> + Mul<Output = N> + Default + Copy {
    let mut total = N::default();
    for i in 0..v1.len() {
        total = total + v1[i] * v2[i];
    }
    total
}

一步一步解释:如果要对N进行约束,那么就需要设置where。


trait Convention {
    type Item;
    fn display(&self);
}

struct Person {
    name: String
}

struct Friend {
    name : String
}
impl Convention for Friend {
    type Item = String;

    fn display(&self) {
        println!("Friend's name is: {}", self.name);
    }
}
impl Person {
    pub fn person_make_friend<'a, T>(&self, friend: &'a T) -> &'a T
    where
        T: Convention,
        T::Item: Debug,
    {
        friend.display();
        friend // 返回朋友实例
    }
}

#[test]
pub fn person_test(){
    let person = Person {
        name: "angel".to_string(),
    };

    let friend = Friend {
        name: "bob".to_string(),
    };

    let friend_return = person.person_make_friend(&friend);
    print!("friend name {}", friend_return.name);
}

想要理解trait中的type:

因为rust中的trait中没有struct定义的具体类型,所以需要在trait中声明type,这样就可以在trait中的多方法里,可以将type作为作为参数或者返回类型,是这样吧。

在 Rust 中,trait 可以包含关联类型(associated types),这允许你在 trait 中定义一个类型占位符,然后在具体实现中指定这个类型。这使得 trait 更加灵活和强大,因为你可以在 trait 的方法中使用这个类型,而不必在每个方法中都显式地传递类型参数。

  I: Iterator<Item = String>, 
  I::Item: Debug 

就理解为:I必须实现迭代器,并且I中的元素是String类型,且约束 `I` 的 `Item`(即迭代器产生的元素类型)必须实现 `Debug` 特征。

也就是说,I是一个集合,I中的元素是String。

有Self可以用了,为什么还用type呢?

//TODO

Rhs的作用

当然可以!让我们为每个点提供一个简单的例子。

### 1. `Rhs = Self`

`Rhs = Self` 是一个默认类型参数。如果用户没有为 `Rhs` 提供具体的类型,那么默认情况下 `Rhs` 就是 `Self`。

#### 示例

```rust

trait Add<Rhs = Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}

struct MyType;

impl Add for MyType {
    type Output = MyType;

    fn add(self, _rhs: MyType) -> MyType {
        MyType
    }
}

impl Add<i32> for MyType {
    type Output = MyType;

    fn add(self, _rhs: i32) -> MyType {
        MyType
    }
}

fn main() {
    let a = MyType;
    let b = MyType;
    let c = a.add(b); // 使用默认的 Rhs = Self

    let d = MyType;
    let e = d.add(42); // 使用 Rhs = i32
}

```

在这个例子中,`Add` trait 有一个默认的类型参数 `Rhs = Self`。当我们没有指定 `Rhs` 时,默认情况下 `Rhs` 就是 `Self`。因此,第一个 `add` 方法实现了 `MyType + MyType`,而第二个 `add` 方法实现了 `MyType + i32`。

### 2. `trait: 另一个trait`

`trait: 另一个trait` 表示一个 trait 继承自另一个 trait。这意味着实现该 trait 的类型也必须实现另一个 trait。

#### 示例

```rust

trait Display {
    fn display(&self);
}

trait Printable: Display {
    fn print(&self);
}

struct MyType;

impl Display for MyType {
    fn display(&self) {
        println!("Displaying MyType");
    }
}

impl Printable for MyType {
    fn print(&self) {
        println!("Printing MyType");
    }
}

fn main() {
    let my_type = MyType;
    my_type.display(); // 调用 Display trait 方法
    my_type.print();   // 调用 Printable trait 方法
}

```

在这个例子中,`Printable` trait 继承自 `Display` trait。因此,任何实现 `Printable` 的类型也必须实现 `Display`。`MyType` 实现了这两个 trait,所以可以调用 `display` 和 `print` 方法。

### 3. `Rhs: ?Sized`

`Rhs: ?Sized` 表示 `Rhs` 类型参数可以是不确定大小的类型,使得 trait 更加灵活。

#### 示例

```rust

trait Compare<Rhs: ?Sized> {
    fn compare(&self, other: &Rhs) -> bool;
}

struct MyType;

impl Compare<MyType> for MyType {
    fn compare(&self, _other: &MyType) -> bool {
        true
    }
}

impl Compare<dyn ToString> for MyType {
    fn compare(&self, _other: &dyn ToString) -> bool {
        true
    }
}

fn main() {
    let a = MyType;
    let b = MyType;
    let result = a.compare(&b); // 比较两个 MyType

    let c = MyType;
    let d: &dyn ToString = &"hello".to_string();
    let result2 = c.compare(d); // 比较 MyType 和 dyn ToString
}

```

在这个例子中,`Compare` trait 接受一个类型参数 `Rhs`,并且 `Rhs` 可以是不确定大小的类型(使用 `?Sized`)。这允许我们实现 `Compare<dyn ToString>`,使得我们可以比较 `MyType` 和实现 `ToString` 的动态大小类型。

通过这些简单的例子,希望你能更好地理解 `Rhs = Self`、`trait: 另一个trait` 和 `Rhs: ?Sized` 在 Rust 中的作用。

也就是rhs默认是self,那么impl Add for MyType,就是自己类型+自己类型,如果rhs=i32,那么就是自己类型+i32类型,即rhs即操作数的右边类型。

而?Sized的动态大小怎么理解:

在 Rust 中,类型可以是“确定大小的”(sized)或“动态大小的”(dynamically sized)。确定大小的类型在编译时其大小是已知的,而动态大小的类型在编译时其大小是不确定的,需要通过引用或指针来处理。例如,`str` 和 trait 对象(如 `dyn ToString`)就是动态大小的类型。

### 动态大小类型(Dynamically Sized Types, DST)

动态大小类型(DST)在编译时其大小是未知的。我们无法直接在栈上分配动态大小类型的值,但可以通过引用或指针来处理它们。常见的动态大小类型包括:

- `str`:字符串切片

- `[T]`:切片

- `dyn Trait`:trait 对象

### `Rhs: ?Sized` 的含义

`Rhs: ?Sized` 中的 `?Sized` 表示 `Rhs` 可以是一个确定大小的类型,也可以是一个动态大小的类型。`?Sized` 是一种特殊的 trait bound,它允许类型参数是动态大小的类型。

### 示例

让我们通过一个具体的例子来理解 `Rhs: ?Sized` 的作用。

```rust

trait Compare<Rhs: ?Sized> {
    fn compare(&self, other: &Rhs) -> bool;
}

struct MyType;

impl Compare<MyType> for MyType {
    fn compare(&self, _other: &MyType) -> bool {
        println!("Comparing MyType with MyType");
        true
    }
}

impl Compare<dyn ToString> for MyType {
    fn compare(&self, _other: &dyn ToString) -> bool {
        println!("Comparing MyType with dyn ToString");
        true
    }
}

fn main() {
    let a = MyType;
    let b = MyType;
    let result = a.compare(&b); // 比较两个 MyType

    let c = MyType;
    let d: &dyn ToString = &"hello".to_string();
    let result2 = c.compare(d); // 比较 MyType 和 dyn ToString
}

```

### 解释

1. **确定大小的类型**:

- 当我们调用 `a.compare(&b)` 时,`a` 和 `b` 都是 `MyType` 类型。由于 `MyType` 是确定大小的类型,编译器知道它的大小,因此可以直接在栈上分配。

- `impl Compare<MyType> for MyType` 处理这个情况。

2. **动态大小的类型**:

- 当我们调用 `c.compare(d)` 时,`c` 是 `MyType` 类型,而 `d` 是一个 `dyn ToString` 类型的引用。`dyn ToString` 是一个动态大小的类型,因为编译器在编译时不知道实现 `ToString` trait 的具体类型的大小。

- `impl Compare<dyn ToString> for MyType` 处理这个情况。由于 `dyn ToString` 是动态大小的类型,我们通过引用 `&dyn ToString` 来传递它。

### 总结

`Rhs: ?Sized` 允许 `Rhs` 类型参数既可以是确定大小的类型,也可以是动态大小的类型。这使得 trait 更加灵活,可以处理更多种类的类型。动态大小类型通常通过引用或指针来处理,因为它们的大小在编译时是不确定的。