在特征实现的上下文中理解 'self' 参数

Understanding the 'self' parameter in the context of trait implementations

在实现trait的时候,我们经常会用到关键字self,示例如下。我想了解此代码示例中 self 的多种用途的表示形式。

struct Circle {
    x: f64,
    y: f64,
    radius: f64,
}

trait HasArea {
    fn area(&self) -> f64;          // first self: &self is equivalent to &HasArea
}

impl HasArea for Circle {
    fn area(&self) -> f64 {         //second self: &self is equivalent to &Circle
        std::f64::consts::PI * (self.radius * self.radius) // third:self
    }
}

我的理解是:

  1. 第一个self&self相当于&HasArea
  2. 第二个self&self等价于&Circle
  3. 第三个self代表Circle吗?如果是这样,如果 self.radius 被使用了两次,那会导致移动问题吗?

此外,如果能提供更多示例来展示 self 关键字在不同上下文中的不同用法,我们将不胜感激。

你基本上是对的。

我的想法是,在方法签名中,self 是一个 shorthand:

impl S {
    fn foo(self) {}      // equivalent to fn foo(self: S)
    fn foo(&self) {}     // equivalent to fn foo(self: &S)
    fn foo(&mut self) {} // equivalent to fn foo(self: &mut S)
}

实际上 不等价,因为 self 是关键字并且有一些特殊规则(例如生命周期省略),但它非常接近。

回到你的例子:

impl HasArea for Circle {
    fn area(&self) -> f64 {   // like fn area(self: &Circle) -> ... 
        std::f64::consts::PI * (self.radius * self.radius)
    }
}

正文中的self类型为&Circle。你不能移出一个参考,所以 self.radius 一次都不能移动。在本例中 radius 实现了 Copy,所以它只是被复制出来而不是被移动。如果它是一个没有实现 Copy 的更复杂的类型,那么这将是一个错误。

你基本上是对的。


有一个巧妙的技巧可以让编译器告诉您变量的类型,而不是试图推断它们:let () = ...;.

使用 the Playground 我得到第一种情况:

9 |         let () = self;
  |             ^^ expected &Self, found ()

对于第二种情况:

16 |         let () = self;
   |             ^^ expected &Circle, found ()

第一种情况其实比较特殊,因为HasArea不是类型,是特征

那么self是什么?还没有.

换句话说,它为 可以实现 HasArea 的任何可能的具体类型 提出。因此,我们对这个特性的唯一保证是它提供 至少 HasArea.

的接口

关键是您可以放置​​额外的边界。例如你可以说:

trait HasArea: Debug {
    fn area(&self) -> f64;
}

而在这种情况下,Self: HasArea + Debug,意味着 self 提供 HasAreaDebug 的接口。


第二种和第三种情况要简单得多:我们知道实现 HasArea 特性的 确切具体类型 。这是 Circle.

因此fn area(&self)方法中self的类型为&Circle.

请注意,如果参数的类型是 &Circle,那么它在方法中的所有使用都是 &Circle。 Rust 具有静态类型(并且没有依赖流的类型),因此给定绑定的类型在其生命周期内不会改变。


然而,事情会变得更加复杂。

假设您有两个特征:

struct Segment(Point, Point);

impl Segment {
    fn length(&self) -> f64;
}

trait Segmentify {
    fn segmentify(&self) -> Vec<Segment>;
}

trait HasPerimeter {
    fn has_perimeter(&self) -> f64;
}

然后,您可以为所有可以细分为一系列线段的形状自动实施 HasPerimeter

impl<T> HasPerimeter for T
    where T: Segmentify
{
    // Note: there is a "functional" implementation if you prefer
    fn has_perimeter(&self) -> f64 {
        let mut total = 0.0;
        for s in self.segmentify() { total += s.length(); }
        total
    }
}

这里的self是什么类型?这是 &T.

什么是 T?任何实现 Segmentify.

的类型

因此,我们对 T 的了解只是它实现了 SegmentifyHasPerimeter,没有别的(我们不能使用 println("{:?}", self); 因为 T 不保证实现 Debug).