我们可以在core::ops
中实现特征来定义我们类型的运算符的行为。特征本身用#[lang =...]
属性注释,因此编译器知道哪些特征和操作符属于一起。
例如,基本类型的Add
实现如下所示(宏从here手动展开和简化):
impl Add for i32 {
type Output = i32;
fn add(self, other: i32) -> i32 {
self + other
}
}
令我惊讶的是,该实现在内部使用+
运算符,可能会调用self.add(other)
,从而导致无限递归。显然,事情不会发生这种情况,因为像3 + 4
这样的表达式(假设没有不断的折叠)可以很好地工作。
现在考虑Add
特性的这种天真实现:
use std::ops::Add;
struct Foo;
impl Add for Foo {
type Output = Foo;
fn add(self, other: Foo) -> Foo {
self + other
}
}
fn main() {
let two_foo = Foo + Foo;
}
编译器警告function cannot return without recurring
并在调试模式下运行此程序正确停止fatal runtime error: stack overflow
。
编译器如何知道如何添加两个数字而不会陷入递归漏洞?
答案 0 :(得分:7)
编译器如何知道添加两个数字而不会陷入递归漏洞?
因为编译器是编译器,并且编译器知道它不需要Add
实现来添加两个数字。如果它正在进行恒定折叠,它只是添加它们。如果它正在生成代码,它会告诉LLVM在运行时添加它们。
那些Add
实现不是告诉编译器如何添加数字,它们是为数字实现Add
,以便用户代码可以通过Add
特征添加数字就像任何用户定义的类型一样。如果这些实现不存在,那么您将无法在泛型方法中添加数字,因为它们不会实现Add
。
换句话说:Add
是编译器在不知道如何添加内容时使用的内容。但它已经知道如何添加数字,所以它不需要它们。它们与其他类型一致。
答案 1 :(得分:5)
最终依赖于加法运算符Add
的{{1}}的实现需要指向基元上的操作(例如整数)和那些上的算术运算使用compiler built-ins实现。
更重要的是,原语本身也是编译器内置函数 - 请注意,您无法在+
文档中找到它们的源代码。
底线是原始类型实际上需要 std
和其他算术运算符'的实现所提供的代码。特征 - 这些功能由编译器的内在函数提供。他们的特质实现是为了泛型的目的而提供的。