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

ena*_*naJ 4 self traits rust

在实现特征时,我们经常使用关键字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
    }
}
Run Code Online (Sandbox Code Playgroud)

我的理解是:

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

此外,self将非常感谢更多示例以显示在不同上下文中关键字的不同用法.

Chr*_*son 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)
}
Run Code Online (Sandbox Code Playgroud)

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

回到你的例子:

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

self在主体的类型为&Circle.你不能移出参考,所以self.radius即使一次也不能移动.在这种情况下radius实现Copy,所以它只是被复制而不是移动.如果它是一个没有实现的更复杂的类型,Copy那么这将是一个错误.


Mat*_* M. 5

您基本上是正确的。


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

使用操场我得到第一种情况:

9 |         let () = self;
  |             ^^ expected &Self, found ()
Run Code Online (Sandbox Code Playgroud)

对于第二种情况:

16 |         let () = self;
   |             ^^ expected &Circle, found ()
Run Code Online (Sandbox Code Playgroud)

第一种情况实际上很特殊,因为HasArea它不是类型,而是特征。

那是什么self?这是什么还没有

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

关键是您可以放置​​其他边界。例如,您可以说:

trait HasArea: Debug {
    fn area(&self) -> f64;
}
Run Code Online (Sandbox Code Playgroud)

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


第二和第三种情况则容易得多:我们知道确切的具体类型为其HasArea特征实现。是Circle

因此,类型selffn 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;
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

什么T啊 实现的任何类型Segmentify

因此,我们所知道的T仅是它实现了SegmentifyHasPerimeter,其他都没有(我们不能使用,println("{:?}", self);因为T不能保证实现Debug)。