src/memory-management/move.md
An assignment will transfer ownership between variables:
# // Copyright 2023 Google LLC
# // SPDX-License-Identifier: Apache-2.0
#
fn main() {
let s1 = String::from("Hello!");
let s2 = s1;
dbg!(s2);
// dbg!(s1);
}
s1 to s2 transfers ownership.s1 goes out of scope, nothing happens: it does not own anything.s2 goes out of scope, the string data is freed.Before move to s2:
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -.
: : : :
: s1 : : :
: +-----------+-------+ : : +----+----+----+----+----+----+ :
: | ptr | o---+---+-----+-->| H | e | l | l | o | ! | :
: | len | 6 | : : +----+----+----+----+----+----+ :
: | capacity | 6 | : : :
: +-----------+-------+ : : :
: : `- - - - - - - - - - - - - - - - - - -'
: :
`- - - - - - - - - - - - - -'
After move to s2:
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - - - - - - - - -.
: : : :
: s1 "(inaccessible)" : : :
: +-----------+-------+ : : +----+----+----+----+----+----+ :
: | ptr | o---+---+--+--+-->| H | e | l | l | o | ! | :
: | len | 6 | : | : +----+----+----+----+----+----+ :
: | capacity | 6 | : | : :
: +-----------+-------+ : | : :
: : | `- - - - - - - - - - - - - - - - - - -'
: s2 : |
: +-----------+-------+ : |
: | ptr | o---+---+--'
: | len | 6 | :
: | capacity | 6 | :
: +-----------+-------+ :
: :
`- - - - - - - - - - - - - -'
When you pass a value to a function, the value is assigned to the function parameter. This transfers ownership:
# // Copyright 2023 Google LLC
# // SPDX-License-Identifier: Apache-2.0
#
fn say_hello(name: String) {
println!("Hello {name}")
}
fn main() {
let name = String::from("Alice");
say_hello(name);
// say_hello(name);
}
Mention that this is the opposite of the defaults in C++, which copies by
value unless you use std::move (and the move constructor is defined!).
It is only the ownership that moves. Whether any machine code is generated to manipulate the data itself is a matter of optimization, and such copies are aggressively optimized away.
Simple values (such as integers) can be marked Copy (see later slides).
In Rust, clones are explicit (by using clone).
In the say_hello example:
say_hello, main gives up ownership of name.
Afterwards, name cannot be used anymore within main.name will be freed at the end of the
say_hello function.main can retain ownership if it passes name as a reference (&name) and
if say_hello accepts a reference as a parameter.main can pass a clone of name in the first call
(name.clone()).Modern C++ solves this differently:
std::string s1 = "Cpp";
std::string s2 = s1; // Duplicate the data in s1.
s1 is duplicated and s2 gets its own independent copy.s1 and s2 go out of scope, they each free their own memory.Before copy-assignment:
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - -.
: : : :
: s1 : : :
: +-----------+-------+ : : +----+----+----+ :
: | ptr | o---+---+--+--+-->| C | p | p | :
: | len | 3 | : : +----+----+----+ :
: | capacity | 3 | : : :
: +-----------+-------+ : : :
: : `- - - - - - - - - - - -'
`- - - - - - - - - - - - - -'
After copy-assignment:
Stack Heap
.- - - - - - - - - - - - - -. .- - - - - - - - - - - -.
: : : :
: s1 : : :
: +-----------+-------+ : : +----+----+----+ :
: | ptr | o---+---+--+--+-->| C | p | p | :
: | len | 3 | : : +----+----+----+ :
: | capacity | 3 | : : :
: +-----------+-------+ : : :
: : : :
: s2 : : :
: +-----------+-------+ : : +----+----+----+ :
: | ptr | o---+---+-----+-->| C | p | p | :
: | len | 3 | : : +----+----+----+ :
: | capacity | 3 | : : :
: +-----------+-------+ : : :
: : `- - - - - - - - - - - -'
`- - - - - - - - - - - - - -'
Key points:
C++ has made a slightly different choice than Rust. Because = copies data,
the string data has to be cloned. Otherwise we would get a double-free when
either string goes out of scope.
C++ also has std::move, which is used to indicate when a value may be
moved from. If the example had been s2 = std::move(s1), no heap allocation
would take place. After the move, s1 would be in a valid but unspecified
state. Unlike Rust, the programmer is allowed to keep using s1.
Unlike Rust, = in C++ can run arbitrary code as determined by the type that
is being copied or moved.