Avatar ·

В Rust: в чем разница между clone() и to_owned() и когда что использовать?

Привет, коллеги! Я начал изучать Rust недавно и постоянно путаюсь в методах clone() и to_owned(). Вроде бы оба создают копию данных, но в документации написано, что это разные вещи. На практике я вижу, что clone() можно вызвать на любом типе, который реализует трейт Clone, а to_owned() — только на типах с трейтом ToOwned. Но разве это не одно и то же? Вот конкретный пример, который меня сбивает с толку: ```rust let s: &str = "hello"; let a: String = s.to_owned(); let b: String = s.to_string(); // тоже работает // А почему я не могу написать s.clone() чтобы получить String? // Ведь clone() возвращает &str, а не String! ``` Другой пример: я работаю со срезами. У меня есть &[i32], и я хочу получить Vec. Я могу сделать slice.to_owned() — это работает. Но если я сделаю slice.clone(), то получу опять &[i32], а не Vec. Почему так? Мне кажется, что to_owned() — это какой-то волшебный метод, который "превращает" заимствованные данные во владеющие. А clone() просто копирует то, что есть, не меняя тип. Но я не уверен, правильно ли я понимаю. Объясните, пожалуйста, на пальцах: в чем фундаментальная разница? Когда я должен использовать clone(), а когда to_owned()? Есть ли случаи, когда они взаимозаменяемы? И что делать, если я хочу клонировать что-то сложное, например, Vec? Заранее спасибо за помощь! <<>>

Основное различие: семантика владения против семантики копирования

Вы на верном пути. Ключевое различие между clone() и to_owned() лежит в семантике владения, а не просто в механике копирования.

clone() — это общий метод, определенный в трейте Clone. Он гарантирует, что после вызова у вас будет точная копия исходного значения, но того же самого типа. Если вы вызываете clone() на &str, вы получаете &str. Если на Vec<i32> — получаете Vec<i32>. Тип не меняется.

to_owned() — это метод из трейта ToOwned. Его цель — создать владеющий вариант (owned variant) из заимствованного (borrowed). Он меняет тип: из ссылки делает полноценное владеющее значение. Например:

let slice: &[i32] = &[1, 2, 3];
let vec: Vec<i32> = slice.to_owned(); // Работает! Тип изменился с &[i32] на Vec<i32>

let s: &str = "hello";
let string: String = s.to_owned(); // Работает! Тип изменился с &str на String

// А вот clone() бы дал:
let same_slice: &[i32] = slice.clone(); // Тип не изменился
let same_str: &str = s.clone();         // Тип не изменился

Теперь разберем ваш пример с to_string(). to_string() — это частный случай to_owned() для типа str. Он реализован через трейт ToString, который автоматически реализован для всех типов, реализующих Display или ToOwned. По сути, to_string() — это синоним to_owned() для строк.

Когда что использовать?

  • Используйте clone(), когда вам нужна копия данных того же самого типа. Например, вы хотите размножить Vec<String>, чтобы передать его в разные функции, и при этом сохранить оригинал.
  • Используйте to_owned(), когда у вас есть заимствованные данные (ссылка, срез), и вы хотите получить их владеющую версию, чтобы, например, вернуть из функции или сохранить в структуре.

Сложный пример: Vec<String>

Предположим, у вас есть vec: Vec<String>. Что произойдет, если вы вызовете clone()?

let original: Vec<String> = vec!["hello".to_string(), "world".to_string()];
let cloned: Vec<String> = original.clone(); // Глубокая копия: каждый String клонируется
// original все еще существует и не тронут

Это глубокая копия. Каждый элемент String внутри вектора будет скопирован. Если бы вы написали to_owned() на &Vec<String>, результат был бы тем же самым, но to_owned() здесь избыточен, так как Vec<String> уже владеющий тип. to_owned() имеет смысл только для &Vec<String>.

Идиоматический паттерн

В реальном коде Rust вы часто увидите такой паттерн:

fn process(data: &[u8]) {
    // Нам нужно передать данные в другой поток, который требует владения
    let owned_data: Vec<u8> = data.to_owned();
    std::thread::spawn(move || {
        // используем owned_data
    });
}

Здесь to_owned() — самый правильный выбор, потому что он явно показывает намерение: "я беру эти заимствованные байты и делаю их своими". Если бы вы написали data.clone(), это сработало бы (потому что &[u8] реализует Clone), но результат был бы опять &[u8], а не Vec<u8>. Компилятор бы выдал ошибку, потому что вы не можете передать ссылку в новый поток без указания времени жизни.

Резюме

  • clone() — "скопируй то, что есть, тип не меняй".
  • to_owned() — "возьми ссылку и сделай из нее владеющее значение, тип изменится".
  • Они не взаимозаменяемы в общем случае, хотя для некоторых типов (например, String) результат может быть одинаковым.
  • В сомнительных случаях выбирайте to_owned(), когда работаете с заимствованными данными, и clone() — когда с владеющими.
<<>>

Альтернативный взгляд: производительность и идиомы

Я бы добавил, что выбор между clone() и to_owned() часто диктуется не только семантикой, но и производительностью, а также читаемостью кода.

Производительность: В большинстве случаев to_owned() и clone() делают одно и то же под капотом — вызывают clone() для каждого элемента. Например, реализация to_owned() для &[T] выглядит так:

fn to_owned(&self) -> Vec<T> {
    self.iter().cloned().collect()
}

То есть это просто цикл с вызовом clone(). Разницы в скорости нет.

Идиоматичность: В сообществе Rust сложилась практика использовать to_owned() именно для преобразования заимствованных типов во владеющие. Это делает код самодокументируемым. Если вы видите .to_owned(), вы сразу понимаете: "ага, тут была ссылка, а теперь владение". Если вы видите .clone() на ссылке, это может вызвать вопрос: "зачем клонировать ссылку, если можно просто разыменовать?"

Исключение: Для типа String многие предпочитают .to_string() вместо .to_owned(), потому что это короче и более явно указывает на целевой тип. Но технически это одно и то же.

Мой совет: не зацикливайтесь на этом. Оба метода работают. Со временем вы начнете чувствовать, какой из них уместнее в конкретном контексте. Главное — чтобы код компилировался и делал то, что нужно.