Святая Корова! (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.
Возможная сложная обработка времён жизни упрощена одним обобщённым временем жизни в функции, плюс две аннотации для аргумента и результата. К тому же я могу использовать функцию везде в моем коде, не волнуясь о временах жизни.
Итак, возможно опускание времени жизни и укусит меня когда-то. Но сегодня был явно не этот день.
Дальше читайте Продолжение…