我正在学习/试验 Rust,在我发现的这门语言的所有优雅中,有一个特点让我感到困惑,似乎完全不合适。
Rust在进行方法调用时会自动取消对指针的引用。我做了一些测试来确定确切的行为:
struct X { val: i32 }
impl std::ops::Deref for X {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
trait M { fn m(self); }
impl M for i32 { fn m(self) { println!("i32::m()"); } }
impl M for X { fn m(self) { println!("X::m()"); } }
impl M for &X { fn m(self) { println!("&X::m()"); } }
impl M for &&X { fn m(self) { println!("&&X::m()"); } }
impl M for &&&X { fn m(self) { println!("&&&X::m()"); } }
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
struct Y { val: i32 }
impl std::ops::Deref for Y {
type Target = i32;
fn deref(&self) -> &i32 { &self.val }
}
struct Z { val: Y }
impl std::ops::Deref for Z {
type Target = Y;
fn deref(&self) -> &Y { &self.val }
}
#[derive(Clone, Copy)]
struct A;
impl M for A { fn m(self) { println!("A::m()"); } }
impl M for &&&A { fn m(self) { println!("&&&A::m()"); } }
impl RefM for A { fn refm(&self) { println!("A::refm()"); } }
impl RefM for &&&A { fn refm(&self) { println!("&&&A::refm()"); } }
fn main() {
// I'll use @ to denote left side of the dot operator
(*X{val:42}).m(); // i32::m() , Self == @
X{val:42}.m(); // X::m() , Self == @
(&X{val:42}).m(); // &X::m() , Self == @
(&&X{val:42}).m(); // &&X::m() , Self == @
(&&&X{val:42}).m(); // &&&X:m() , Self == @
(&&&&X{val:42}).m(); // &&&X::m() , Self == *@
(&&&&&X{val:42}).m(); // &&&X::m() , Self == **@
println!("-------------------------");
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
println!("-------------------------");
Y{val:42}.refm(); // i32::refm() , Self == *@
Z{val:Y{val:42}}.refm(); // i32::refm() , Self == **@
println!("-------------------------");
A.m(); // A::m() , Self == @
// without the Copy trait, (&A).m() would be a compilation error:
// cannot move out of borrowed content
(&A).m(); // A::m() , Self == *@
(&&A).m(); // &&&A::m() , Self == &@
(&&&A).m(); // &&&A::m() , Self == @
A.refm(); // A::refm() , Self == @
(&A).refm(); // A::refm() , Self == *@
(&&A).refm(); // A::refm() , Self == **@
(&&&A).refm(); // &&&A::refm(), Self == @
}
(游乐场)
所以,似乎或多或少:
确切的自动取消引用规则是什么?有人能给出这样一个设计决策的正式理由吗?
这个问题困扰了我很久,尤其是对于这一部分:
(*X{val:42}).refm(); // i32::refm() , Self == @
X{val:42}.refm(); // X::refm() , Self == @
(&X{val:42}).refm(); // X::refm() , Self == *@
(&&X{val:42}).refm(); // &X::refm() , Self == *@
(&&&X{val:42}).refm(); // &&X::refm() , Self == *@
(&&&&X{val:42}).refm(); // &&&X::refm(), Self == *@
(&&&&&X{val:42}).refm(); // &&&X::refm(), Self == **@
直到我找到一种方法来记住这些奇怪的规则。我不确定这是否正确,但大多数时候这种方法是有效的。
关键是,在寻找要使用的函数时,不要使用调用“点运算符”的类型来确定要使用的“impl”,而是根据函数签名找到函数,然后用函数签名确定“self”的类型。
我将函数定义代码转换如下:
trait RefM { fn refm(&self); }
impl RefM for i32 { fn refm(&self) { println!("i32::refm()"); } }
// converted to: fn refm(&i32 ) { println!("i32::refm()"); }
// => type of 'self' : i32
// => type of parameter: &i32
impl RefM for X { fn refm(&self) { println!("X::refm()"); } }
// converted to: fn refm(&X ) { println!("X::refm()"); }
// => type of 'self' : X
// => type of parameter: &X
impl RefM for &X { fn refm(&self) { println!("&X::refm()"); } }
// converted to: fn refm(&&X ) { println!("&X::refm()"); }
// => type of 'self' : &X
// => type of parameter: &&X
impl RefM for &&X { fn refm(&self) { println!("&&X::refm()"); } }
// converted to: fn refm(&&&X ) { println!("&&X::refm()"); }
// => type of 'self' : &&X
// => type of parameter: &&&X
impl RefM for &&&X { fn refm(&self) { println!("&&&X::refm()"); } }
// converted to: fn refm(&&&&X) { println!("&&&X::refm()"); }
// => type of 'self' : &&&X
// => type of parameter: &&&&X
因此,当您编写代码时:
(
函数
<代码>fn refm(
因为参数类型是< code >
如果没有找到匹配的函数签名,则执行自动引用或一些自动deref。
Rust参考有一章是关于方法调用表达式的。我在下面复制了最重要的部分。提醒:我们正在谈论一个表达式recv. m()
,其中recv
在下面称为“接收器表达式”。
第一步是建立候选接收器类型的列表。通过重复取消引用接收方表达式的类型,将遇到的每个类型添加到列表中,然后最后尝试一个未调整大小的强制,如果成功,则添加结果类型。然后,对于每个候选< code>T,添加< code >
例如,如果接收方有< code >框
然后,对于每个候选类型 T
,在以下位置搜索具有该类型接收器的可见方法:
T
的固有方法(直接在T
[CharStyle]上实现的方法)T
实现的可见特征提供的任何方法。[…](关于[1]的注释:我实际上认为这句话是错误的。我提出了一个问题。让我们忽略括号中的那句话。)
让我们详细看一下代码中的几个示例!对于您的示例,我们可以忽略有关“未放大的强制”和“固有方法”的部分。
< code>(*X{val:42})。m():接收方表达式的类型是< code>i32。我们执行以下步骤:
i32
无法取消引用,因此我们已经完成了第1步。列表:[i32]
我们发现<code>
这是一个很好的例子
到目前为止很容易。现在让我们选择一个更难的例子:
(
正在创建候选接收器类型列表:
没有接收器类型的方法
以下是所有示例的候选接收器列表。包含在<code>中的类型⟪x⟫
是“赢”的类型,即可以找到拟合方法的第一种类型。还要记住,列表中的第一个类型始终是接收方表达式的类型。最后,我将列表格式化为三行,但这只是格式化:这个列表是一个平面列表。
(*X{val:42}).m()
→ <i32 as M>::m
[⟪i32⟫, &i32, &mut i32]
[⟪X⟫, &X, &mut X,
i32, &i32, &mut i32]
[⟪&X⟫, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[⟪&&X⟫, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[&&&&X, &&&&&X, &mut &&&&X,
⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[&&&&&X, &&&&&&X, &mut &&&&&X,
&&&&X, &&&&&X, &mut &&&&X,
⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
(*X{val:42}).refm()
→ <i32 as RefM>::refm
[i32, ⟪&i32⟫, &mut i32]
[X, ⟪&X⟫, &mut X,
i32, &i32, &mut i32]
[⟪&X⟫, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[⟪&&X⟫, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[⟪&&&X⟫, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[⟪&&&&X⟫, &&&&&X, &mut &&&&X,
&&&X, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
[&&&&&X, &&&&&&X, &mut &&&&&X,
⟪&&&&X⟫, &&&&&X, &mut &&&&X,
&&&X, &&&&X, &mut &&&X,
&&X, &&&X, &mut &&X,
&X, &&X, &mut &X,
X, &X, &mut X,
i32, &i32, &mut i32]
Y{val:42}.refm()
→ <i32 as RefM>::refm
[Y, &Y, &mut Y,
i32, ⟪&i32⟫, &mut i32]
[Z, &Z, &mut Z,
Y, &Y, &mut Y,
i32, ⟪&i32⟫, &mut i32]
A.m()
→ <A as M>::m
[⟪A⟫, &A, &mut A]
[&A, &&A, &mut &A,
⟪A⟫, &A, &mut A]
[&&A, ⟪&&&A⟫, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
[⟪&&&A⟫, &&&&A, &mut &&&A,
&&A, &&&A, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
[A, ⟪&A⟫, &mut A]
[⟪&A⟫, &&A, &mut &A,
A, &A, &mut A]
[&&A, &&&A, &mut &&A,
⟪&A⟫, &&A, &mut &A,
A, &A, &mut A]
[&&&A, ⟪&&&&A⟫, &mut &&&A,
&&A, &&&A, &mut &&A,
&A, &&A, &mut &A,
A, &A, &mut A]
你的伪代码非常正确。对于这个例子,假设我们有一个方法调用< code>foo.bar(),其中< code>foo: T。我将使用完全限定语法(FQS)来明确调用方法的类型,例如< code>A::bar(foo)或< code>A::bar(
该算法的核心是:
值得注意的是,所有内容都考虑了方法的“接收者类型”,而不是特征的Self
类型,即Foo{fn方法的impl(
如果在内部步骤中有多个有效的trait方法,这是一个错误(也就是说,在每个1中只能有零个或一个有效的trait方法。或者2。,但是每一个都可以有一个有效的方法:首先取1中的方法),并且固有方法优先于特征方法。如果我们到达循环的末尾却没有找到任何匹配的东西,这也是一个错误。拥有递归的< code>Deref
实现也是错误的,这会使循环无限循环(它们将达到“递归极限”)。
这些规则似乎在大多数情况下都是有意义的,尽管在某些极端情况下,以及对于宏生成代码的合理错误消息,能够编写明确的FQS形式是非常有用的。
仅添加一个自动引用,因为:
假设我们有一个调用<code>foo。refm(),如果foo
具有以下类型:
X
,那么我们从< code>U = X开始,< code>refm的接收器类型为< code >
假设我们有 foo.m()
,如果 foo
有类型,则 A
不是 Copy
:
A
,则< code>U = A直接匹配< code>self,因此调用为< code > M::M(foo) with < code > Self = A < li > <代码>
(这个答案是基于代码的,并且相当接近(略微过时的)自述文件。Niko Matsakis,这部分编译器/语言的主要作者,也瞥了一眼这个答案。
问题内容: 我是GoLang的新手,来自Delphi C ++世界-诚然对这种语言感到非常兴奋,我认为它将成为“下一件大事”。 我正在尝试了解Go解析器和编译器如何处理指针和引用-似乎找不到任何放置一些明确规则的地方。 例如,在下面的代码示例中,返回类型和局部变量是指针类型,并且在其声明中需要使用指针符号,但是在使用时不必取消引用它们:。但是在同一代码中,输入参数被声明为指针,并且必须被取消引用才
以上两段代码,一个返回String的可变引用,一个返回不可变引用,为什么返回可变引用的闭包实现了FnOnce,返回不可变引用的闭包只实现了Fn呢? rust返回引用时,遵循什么规则?和my_s_ref本身有关还是my_string有关?
问题内容: 好吧,首先我应该问一下这是否与浏览器有关。 我已经读到,如果找到了无效的令牌,但是代码段在该无效令牌之前一直有效,如果在该令牌之前加了换行符,则在该令牌之前插入一个分号。 但是,引用由分号插入引起的错误的常见示例是: ..似乎不遵循此规则,因为_a是有效令牌。 另一方面,分解呼叫链可以按预期工作: 是否有人对规则有更深入的描述? 问题答案: 首先,您应该知道哪些语句受自动分号插入(为简
团队, 使用continueAsNew的确切用例是什么? 由于我们支持Cron附表进行定期活动,我不知道使用它的场景。 我们这样做是为了提供向后兼容性吗
那么,换句话说,为什么编译器不能在第二个方法中执行自动装箱呢?是因为在第二个方法中,绑定不是显式的,而在第一个方法中绑定是明确的。
使用指南 - 统计设置 - 统计规则设置 - 什么是统计规则 统计规则指对网站流量数据的分类或者过滤方法。如果您对获取的流量数据有特殊需求,一般可以通过设置统计规则的方式来实现。 例如,如果您希望获取的流量数据中不包含公司内部访问的流量,那么您可以通过排除IP的方式实现;如果您希望按照实际业务内容划分流量数据,那么您可以通过新增子目录的方式实现。