corazza
corazza

Reputation: 32374

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 (e.g. a function is made so that it cleans up a resource moved into it), but performance reasons would also be interesting (e.g. 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 e.g., but in functions that would not be a problem.

Upvotes: 4

Views: 491

Answers (3)

Veedrac
Veedrac

Reputation: 60207

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.

Upvotes: 2

Hauleth
Hauleth

Reputation: 23586

There are some situations like that, all of them are related to lifetime of structure, i.e. I'm writing Octavo (crypto library) where we have some hash functions (i.e. 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)

Upvotes: 2

Vladimir Matveev
Vladimir Matveev

Reputation: 128101

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.

Upvotes: 0

Related Questions