Семантика переміщення

Присвоєння переміщує володіння між змінними:

fn main() {
    let s1: String = String::from("Привіт!");
    let s2: String = s1;
    println!("s2: {s2}");
    // println!("s1: {s1}");
}
  • Присвоєння s1 до s2 переміщує володіння.
  • Коли s1 виходить за межі області видимості, нічого не відбувається: вона нічим не володіє.
  • Коли s2 виходить за межі, дані рядка звільняються.

Перед переміщенням до s2:

StackHeaps1ptrHello!len6capacity6

Після переміщення до s2:

s1ptrHello!len6capacity6s2ptrlen6capacity6(inaccessible)СеткКпуа

Коли ви передаєте значення функції, це значення присвоюється параметру функції. Це переміщує володіння:

fn say_hello(name: String) {
    println!("Привіт {name}")
}

fn main() {
    let name = String::from("Alice");
    say_hello(name);
    // say_hello(name);
}
This slide should take about 5 minutes.
  • Зауважте, що це протилежність поведінки за замовчуванням у C++, яка копіює за значенням, якщо ви не використовуєте std::move (і конструктор переміщення визначено!).

  • Переміщується лише володіння. Чи генерується машинний код для маніпулювання самими даними - це питання оптимізації, і такі копії агресивно оптимізуються.

  • Прості значення (наприклад, цілі числа) можна позначити Copy (див. наступні слайди).

  • У Rust клони є явними (за допомогою clone).

У прикладі say_hello:

  • З першим викликом say_hello main втрачає володіння name. Після цього name більше не можна використовувати в main.
  • Пам’ять купи, виділена для name, буде звільнено в кінці функції say_hello.
  • main може зберігти володіння, якщо передасть name як посилання (&name) і якщо say_hello приймає посилання як параметр.
  • Крім того, main може передати клон name під час першого виклику (name.clone()).
  • Rust ускладнює ненавмисне створення копій, на відмінну від C++, роблячи семантику переміщення за замовчуванням і змушуючи програмістів робити клони явними.

Більше інформації для вивчення

Захисні копії в сучасному C++

Сучасний C++ вирішує це інакше:

std::string s1 = "Cpp";
std::string s2 = s1;  // Дублювання даних в s1.
  • Дані купи з s1 дублюються, а s2 отримує власну незалежну копію.
  • Коли s1 і s2 виходять за межі видимості, кожен з них звільняє власну пам'ять.

Перед копіюванням:

StackHeaps1ptrCpplen3capacity3

Після копіювання:

StackHeaps1ptrCpplen3capacity3s2ptrCpplen3capacity3

Ключові моменти:

  • C++ зробив дещо інший вибір, ніж Rust. Оскільки = копіює дані, дані рядка потрібно клонувати. Інакше ми отримаємо подвійне звільнення, коли будь-який рядок виходить за межі видимості.

  • C++ також має std::move, який використовується щоб вказати коли значення можна перемістити. Якби приклад був s2 = std::move(s1), розподілу купи не відбулося б. Після переміщення s1 буде в діючому, але не визначеному стані. На відміну від Rust, програмісту дозволено використовувати s1.

  • На відміну від Rust, = у C++ може виконувати довільний код, який визначається типом, який копіюється або переміщується.