简体   繁体   中英

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.

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.

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):

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 . If T was taken by reference, all operations on U would be required to uphold the invariants in T . This means that one would not be allowed to destroy T in any way. Further, the caller would be responsible for keeping the input in place in order for U to remain valid. A good example is Vec::into_boxed_slice .

Another example is a function that moves its input. Evidently this cannot accept a &mut without worrisome semantics. An example would be Vec::insert .

Another option is when &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.

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 .

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. 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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