Святая Корова! — Продолжение (Holy std: borrow: Cow! — Redux)
Последний раз я использовал
очень полезный Cow
, чтобы разобраться с тем, нужно ли клонировать str
или
просто заимствовать её. Это было моё первое применение аннотаций времён жизни, и
это было явно похоже на достижение. : -)
На /r/rust, пользователь Artemciy спросил меня очень хороший вопрос
[…] Как это работает? Похоже на какую-то магию. Имею ввиду, как String становится str?
А также предложил свой вариант ответа:
P. S. Похоже, что в
String
есть реализацияIntoCow
’, которая преобразует её вstr
, но если посмотреть на эту реализацию — опять какая-то магия. Может String в тайне является str?
(выделено мной)
Хороший вопрос. На самом деле, наверное, вы бы хотели прочитать про обмен догадками, озарение, исследования, неправильные решения и драму в интернете. Но увы! У кого есть на это время? Поэтому попытаюсь донести всю суть от начала и до конца.
Первой подсказкой является сам Cow
. Если посмотреть на сырой
код,
можно найти следующее (документация модуля и некоторые дополнения):
pub enum Cow<'a, B: ?Sized + 'a> where B: ToOwned {
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned)
}
Я не буду обсуждать различные реализации Into
и From
, которые объединяют
всех Cow
, String
и другие типы; можете посмотреть их в документации к
библиотеке. Вещи по-настоящему гениальны, а тот, кто их добавил, заслуживает
Премии Тьюринга.
Как мы помним с недавнего времени, у нас был тип Cow<'a, str>
с временем жизни
'a
, который мы получали из аргумента default. Но это вообще ничего не
говорит о str
или String
! Где же тут реализация from?
Подсказкой была граница типажа ToOwned
, который реализован для str
.
Глядя на неё,
можно выделить следующее определение (укорочено здесь):
impl ToOwned for str {
type Owned = String;
fn to_owned(&self) -> String { ... }
}
Итак, большая часть мозаики сложилась. Определение содержит ассоциированный
тип, называемый Owned
. В случае str
, тип Owned
это…
String
. Та-дамс!
Он определяет также, как получить String
из str
, к этому мы ещё вернёмся
попозже. Итак, наш Cow<'a, str>
может стать следующим:
Borrowed(&'a str)
Owned(String)
именно это мы и хотели. Если мы выполним Deref
или Borrow
нашего Cow, мы
получим из него &'a str
(в случае владения, он просто заимствует наш обёрнутый
обладаемый экземпляр), а если мы просим .to_owned()
, мы всегда получим
String
(в случае реализации ToOwned
, приведённой выше).
Отлично. Но это ещё не конец. Мы не ответили на вопрос, правда ли что наш такой
простой String
на самом деле ведёт двойную жизнь, и становится str
при
полной луне, во тьме или когда у него просто подходящее настроение. Или как
сказал Artemciy: Может String в тайне является str?
Я верю, что каждый может поступать так, как он хочет, но вмешиваться в действия
типов Rust все же довольно грубо. Однако, String
действует подозрительно, и
нет ничего лучше хорошей детективной истории. Просто не говорите String
,
откуда мы узнали его маленький секрет, если мы действительно что-то найдём.
Для начала нашего расследования у нас есть подсказки:
&str
. Подождите. Eddy B. объяснил,
что я ошибаюсь: Любой &String
является типом &String
. На самом деле
неявные приведения типов при разыменовании преобразовывают её в &str
,
потому что Deref
так реализован у String
, что ассоциированным
целевым
типом является str
- Когда мы хотим владеть
str
, на самом деле мы получаемString
- Разыменование
&str
и&String
даёт нам одинаковыйstr
Итак, что такое str
? И что такое String
?
В документации сказано следующее про str:
Тип
str
в Rust является одним из примитивных типов в ядре языка.&str
это тип заимствованной строки. Этот тип может быть создан только из другой строки, кроме случаев когда он является&'static str
(см ниже). Нельзя переместить значение из заимствованной строки, потому что в другом месте кто-то владеет им.
Посмотрев в исходный код, str.rs
убеждаемся, что str
это примитивный тип. Это означает, что нет там нет
описания struct
или enum
, а также алиаса type
. Компилятор, очевидно,
понимает str
более ясно, чем с готовностью в этом признается.
Дальше в документации есть раздел представлений, содержащий следующее предложение:
The actual representation of strs have direct mappings to slices:
&str
is the same as&[u8]
. (Настоящее представление str точно совпадает со срезом:&str
это тоже самое, что и&[u8]
).
Итак, str
это просто набор байтов, которые гарантированно являются правильными
UTF-8. &str
таким образом, это заимствованная последовательность байтов, в
то же время String
это…здесь у нас пустота. У нас есть один кусочек пазла,
а второй поищем в collections: string,
где представлено описание String
:
pub struct String {
vec: Vec<u8>,
}
Итак, String
это обёртка вокруг Vec
из байт, конструктор которой
гарантирует, что содержимое всегда будет правильным UTF-8.
Теперь мы видим, что наше подозрение было необоснованным: str
и String
очень
близки друг другу (на самом деле у них такие же отношения, как у содержимого
среза по отношению к Vec
), но это не один человек. Дело закрыто!
Без мучения нет и развлечения, так ведь? Вы же не расскажете String
о нашем
маленьком расследовании, да? Пожалуйста, пообещайте мне…
Отлично. Не только опускание времени жизни все хочет укусить меня за задницу,
теперь ещё и String
жаждет мести. Если обо мне ничего не будет слышно в
течение следующей недели, передайте моей жене, что я её люблю.
Бонус: Главный сыщик diwic поделился результатами расследования шокирующей
связи между Borrow
и ToOwned
в комментарии на /r/rust.