简体   繁体   English

在Rust中实际需要移动函数的情况

[英]Situations where a move to a function is actually wanted in Rust

I'd like to determine in which general (or specific) situations one would not want to use a reference to pass an object into a function in Rust. 我想确定哪些一般(或特定)情况不希望使用引用将对象传递给Rust中的函数。

I'm mostly interested in functionality (eg a function is made so that it cleans up a resource moved into it), but performance reasons would also be interesting (eg if it was the case that it's faster to copy a small structure than access it through a pointer). 我最感兴趣的是功能 (例如,一个函数是为了清理移入其中的资源),但是性能原因也很有趣(例如,如果复制一个小结构比访问它更快)通过指针)。

This regards non- Copy types, of course. 当然,这涉及非Copy类型。

Note: it's clear why move semantics are used in assignments , in order to prevent aliasing eg, but in functions that would not be a problem. 注意:很清楚为什么在分配中使用移动语义,以防止混叠,例如,但在功能中不会出现问题。

There are some situations like that, all of them are related to lifetime of structure, ie I'm writing Octavo (crypto library) where we have some hash functions (ie SHA1): 在某些情况下,所有这些都与结构的生命周期有关,即我正在编写Octavo(加密库),其中我们有一些哈希函数(即SHA1):

let mut digest = SHA1::new();
digest.update("Ala ma kota!");

let result = digest.result(); // here we invalidate digest as it finished its work

Other usage for case like above is builder pattern: 以上情况的其他用法是构建器模式:

PizzaBuilder::new()
    .add_sauce()
    .add_cheese()
    .add_topping(Topping::Tomato)
    .add_topping(Topping::Bacon)
    .add_topping(Topping::Jalapenio)
    .bake() // here we destroy PizzaBuilder as this setup isn't usable  anymore
            // you should create new PizzaBuilder for another pizza
            // (you cannot reuse dough once you bake pizza)

Consider a conversion function fn(T) -> U . 考虑转换函数fn(T) -> U If T was taken by reference, all operations on U would be required to uphold the invariants in T . 如果通过引用获取T则需要在U上的所有操作来维持T的不变量。 This means that one would not be allowed to destroy T in any way. 这意味着不允许任何人以任何方式摧毁T Further, the caller would be responsible for keeping the input in place in order for U to remain valid. 此外,呼叫者将负责保持输入到位以使U保持有效。 A good example is Vec::into_boxed_slice . 一个很好的例子是Vec::into_boxed_slice

Another example is a function that moves its input. 另一个例子是移动其输入的函数。 Evidently this cannot accept a &mut without worrisome semantics. 显然,这不能接受一个&mut而没有令人担忧的语义。 An example would be Vec::insert . 一个例子是Vec::insert

Another option is when &mut Trait: Trait . 另一个选择是什么时候&mut Trait: Trait In this case taking T: Trait allows the caller to decide whether to borrow with dynamic dispatch or pass arguments, which has ramifications for ease of use (in both directions), speed and code bloat. 在这种情况下,使用T: Trait允许调用者决定是否使用动态调度或传递参数进行借用,这些参数具有易于使用(在两个方向上),速度和代码膨胀的分支。

Another example might just be the preference of convenient syntax for the common case, where cloning is cheap. 另一个例子可能只是对常见情况的方便语法的偏好,其中克隆是便宜的。 An example could be Index<Range<usize>> for Vec . 一个例子可能是Vec Index<Range<usize>>

There are also compelling safety reasons if you want to restrict the number of times a function can be called on a given object. 如果要限制在给定对象上调用函数的次数,还有令人信服的安全性原因。 The simplest example is drop , which you rightly can only call once. 最简单的例子是drop ,你只能调用一次。 A very fancy example are "session types" for channel communication. 一个非常奇特的例子是用于频道通信的“会话类型”。

One of applications of move semantics is type-safe state machines. 移动语义的一个应用是类型安全的状态机。 Suppose that you have an object which can be in two states, uninitialized and initialized, and going from uninitialized to initialized has side effects and can possibly fail. 假设您有一个可以处于两种状态的对象,未初始化和初始化,并且从未初始化到初始化具有副作用,并且可能会失败。 This is naturally modeled with two types with a transition method which accepts an object of the first type by value and returns an object of the second type: 这自然地用两种类型建模,其中一种转换方法通过值接受第一种类型的对象并返回第二种类型的对象:

pub struct UninitFoo {
    ...
}

impl UninitFoo {
    pub fn new() -> UninitFoo { ... }
    pub fn configure_something(&mut self) { ... }
    pub fn configure_something else(&mut self) { ... }
    pub fn initialize(self) -> Result<InitFoo, SomeError> { ... }
}

pub struct InitFoo {
    ...
}

impl InitFoo {
    pub fn do_some_work(&mut self) { ... }
}

The example above is really contrived but I guess you get the idea. 上面的例子真是做作,但我想你明白了。 This way your set of types essentially forms a state machine, where methods like initialize() are transitions between states. 这样,您的类型集基本上形成状态机,其中initialize()等方法是状态之间的转换。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM