理解“自我”'特征实现的上下文中的参数

时间:2016-11-01 06:56:04

标签: rust traits self

实现特征时,我们经常使用关键字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,会导致移动问题吗?
  4. 此外,我们将非常感谢在不同背景下显示self关键字的不同用法的更多示例。

2 个答案:

答案 0 :(得分:5)

你大部分都是对的。

我想到的方法是,在方法签名中,self是一种简写:

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的更复杂的类型,那么这将是一个错误。

答案 1 :(得分:5)

你大多是正确的。

有一个巧妙的技巧让编译器告诉你变量的类型,而不是试图推断它们: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

因此,self方法中的fn area(&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)。