在特征实现的上下文中理解 '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
}
}
我的理解是:
- 第一个
self
:&self
相当于&HasArea
。
- 第二个
self
:&self
等价于&Circle
。
- 第三个
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
提供 HasArea
和 Debug
的接口。
第二种和第三种情况要简单得多:我们知道实现 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
的了解只是它实现了 Segmentify
和 HasPerimeter
,没有别的(我们不能使用 println("{:?}", self);
因为 T
不保证实现 Debug
).
在实现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
}
}
我的理解是:
- 第一个
self
:&self
相当于&HasArea
。 - 第二个
self
:&self
等价于&Circle
。 - 第三个
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
提供 HasArea
和 Debug
的接口。
第二种和第三种情况要简单得多:我们知道实现 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
的了解只是它实现了 Segmentify
和 HasPerimeter
,没有别的(我们不能使用 println("{:?}", self);
因为 T
不保证实现 Debug
).