Святая Корова! (Holy std: borrow: Cow!)
На днях я участвовал в обсуждении на reddit того, насколько трудны на самом деле времена жизни. При работе над clippy мне не приходилось до этого времени иметь с ними дела, потому что всем, чем мы пользовались, владел компилятор, особого мнения на этот счёт у меня не было, и тут The_Doculope заявил:
Есть места, в которых опускание времён жизни может укусить нас за задницу.
(/u/The_Doculope на /r/rust)
Честно говоря, я был шокирован!
И вот я, благодарный опусканию времени жизни за то, что освободило меня от лишних переживаний, только что узнал, что оно может укусить меня за задницу. Я начал параноить. ; -P
(я на /r/rust)
Сразу после написания этого, мне попалась строка в исходном коде clippy, которой
я был не очень доволен, потому что в ней происходило клонирование строки (с
помощью .to_string()! богохульство!) просто для использования её в
&format(…). Итак, происходило это в Result<String, …>::unpack_or(…), поэтому
сначала я попытался дать обладаемой строке такое же время жизни, как и у
полученной, без особого смысла.
Увы, все было напрасно. я не мог дать обладаемой строке то же время жизни, что и у заимствованной у компилятора, на что компилятор мне недвусмысленно намекал.
Я избавлю вас от этих покрытых моей кровью сообщений об ошибках, потому что
убеждён — если вы писали код на Rust, вы их знаете наизусть, и, даже наоборот, на
самом деле все было не так уж и плохо, потому что, к счастью, я вспомнил такой
удобный std::borrow::Cow,
а уж переписать сниппет с ним (и std::convert::From) было раз плюнуть. Я даже
создал fn для его повторного использования, таким образом, так я официально
написал свой первый метод, аннотированный временем жизни. [ДОСТИЖЕНИЕ ОТКРЫТО]
Итак что делает Cow (кроме того, что говорит «Муу»)? Cow это аббревиатура Clone- on-write (обычно Copy on Write, но copy имеет другой смысл в Rust), прикольная идея, означающая, что мы можем пользоваться как обладаемым экземпляром чего- либо, так и заимствованным, используя один и тот же код.
Интересующимся, функция выглядит так:
pub fn snippet<'a>(cx: &Context, span: Span, default: &'a str) -> Cow<'a, str> {
cx.sess().codemap().span_to_snippet(span).map(From::from).unwrap_or(Cow::Borrowed(default))
}
Бонус: Наверное, надо объяснить, что делает эта функция:
cx.sess().codemap() извлекает CodeMap из нашей сессии статического анализа. У
неё есть функция получения источника Span (который есть почти у всех элементов
AST). Заметьте, функция span_to_snippet берет span и возвращает
Result<String, SpanSnippetError> (ошибка возвращается для span’ов
неправильного формата).
Итак, у нас есть два случая:
-
Наш Result
.is_ok(). Можем использовать типажFromдля преобразования нашейStringвCow<'a, str>с любым временем жизни. Приведение типов сделает всю грязную работу за нас, все, что нужно этоmap(From::from), и мы в шоколаде. -
У нас
SpanSnippetError. В этом случае мы хотим вернуть значение default, обёрнутое вCow<'a, str>, где'a— это время жизни аргумента default. Вот именно поэтому я написал функцию, обобщённую по времени жизни аргумента default: можем использовать его время жизни для нашей Cow. Для обработки этого случая, получая нашCowиз результата, используемunwrap_or.
Тип Result даёт нам удобные методы по работе с ним в читаемом виде. Так как
Cow<'a, str> может выполнить Deref в str, мы можем напрямую использовать
её в format!. Если мы захотим изменить строку, можем использовать функцию
into_owned() у Cow.
Возможная сложная обработка времён жизни упрощена одним обобщённым временем жизни в функции, плюс две аннотации для аргумента и результата. К тому же я могу использовать функцию везде в моем коде, не волнуясь о временах жизни.
Итак, возможно опускание времени жизни и укусит меня когда-то. Но сегодня был явно не этот день.
Дальше читайте Продолжение…