调用函数时,所有权转移是否会复制“ self”结构?

时间:2019-07-12 17:58:54

标签: rust compiler-optimization

在下面的示例中,cons.push(...)是否复制过self参数?

或者rustc足够聪明,以至于认识到来自行#a#b的值可以始终使用相同的堆栈空间,并且不需要进行复制(明显的{ {1}}个副本)?

换句话说,随着所有权的转移,对i32的调用是否总是创建Cons.push(self, ...)的副本?还是self结构总是保留在堆栈中?

请参考文档。

self

以上示例中的含义是,每次我们以#[derive(Debug)] struct Cons<T, U>(T, U); impl<T, U> Cons<T, U> { fn push<V>(self, value: V) -> Cons<Self, V> { Cons(self, value) } } fn main() { let cons = Cons(1, 2); // #a let cons = cons.push(3); // #b println!("{:?}", cons); // #c } 的速率添加push(...)之类的行时,#b函数的调用成本是否会增加(如果{{每次复制1}}或以O(n^2)的速率复制(如果self留在原处)。

我尝试实现O(n)特质,并注意到selfDrop都在 #a之后被删除了。对我来说,这似乎表明#b在此示例中保持不变,但我不是100%。

2 个答案:

答案 0 :(得分:2)

通常,请信任编译器! Rust + LLVM是一个非常强大的组合,通常会产生令人惊讶的高效代码。而且它将在时间上进一步改善。

  

换句话说,随着所有权被转移,对Cons.push(self,...)的调用是否总是创建self的副本?还是自我结构始终保持在堆栈上?

self无法停留在原处,因为push方法返回的新值的类型为Cons<Self, V>,实际上是SelfV的元组。尽管tuples don't have any memory layout guarantees,但我坚信它们不能在内存中任意分散其元素。因此,selfvalue必须都移到新结构中。

以上段落假定self在调用push之前已牢固地放在堆栈上。编译器实际上有足够的信息,知道它应该为最终结构保留足够的空间。尤其是在使用函数内联时,这很有可能是一种优化。

  

以上示例中的含义是,每当我们以O(n ^ 2)的速率添加#b之类的行时,push(...)函数的调用成本是否会增加(如果复制了self,每次)或以O(n)的速率(如果自我保持原状)。

考虑两个功能(playground):

pub fn push_int(cons: Cons<i32, i32>, x: i32) -> Cons<Cons<i32, i32>, i32> {
    cons.push(x)
}

pub fn push_int_again(
    cons: Cons<Cons<i32, i32>, i32>,
    x: i32,
) -> Cons<Cons<Cons<i32, i32>, i32>, i32> {
    cons.push(x)
}

push_int将第三个元素添加到Cons,而push_int_again添加第四个元素。

push_int在发布模式下编译为以下程序集:

movq    %rdi, %rax
movl    %esi, (%rdi)
movl    %edx, 4(%rdi)
movl    %ecx, 8(%rdi)
retq

然后push_int_again编译为:

movq    %rdi, %rax
movl    8(%rsi), %ecx
movl    %ecx, 8(%rdi)
movq    (%rsi), %rcx
movq    %rcx, (%rdi)
movl    %edx, 12(%rdi)
retq

您不需要了解汇编程序就可以知道推动第四个元素比推动第三个元素需要更多的指令。

请注意,此观察是针对这些功能单独进行的。内联诸如cons.push(x).push(y).push(...)之类的调用,并且程序集每次推入一条指令即可线性增长。

答案 1 :(得分:0)

在#a类型Cons中的con的所有权将在push()中转移。同样,所有权将转移到Cons<Cons,i32>(Cons<T,U>)类型,在#b中为阴影变量cons。

如果struct Cons实现Copy,则克隆特征将被复制。否则,将没有副本,并且在其他人移动(或拥有)原始变量后,您将无法使用它们。

移动语义:

let cons = Cons(1, 2); //Cons(1,2) as resource in memory being pointed by cons
let cons2 = cons; // Cons(1,2)  now pointed by cons2. Problem! as cons also point it. Lets prevent access from cons
println!("{:?}", cons); //error because cons is moved