
Size: a a a
// код опущен для краткостиОчевидно, этот код не работает. И действительно, если мы запустим эту программу, то один из ассертов запустит панику. В данном случае это будет... Будет... Погодите-ка, оно работает? Что?! Но ведь в Rust нет свойств!
fn main() {
let tricky = <Tricky as Default>::default();
assert_eq!(tricky.field, 7);
assert_eq!(tricky.field, 4);
assert_eq!(tricky.field, 13);
let mut nice_try = <NiceTry as Default>::default();
assert_eq!(nice_try.field, 0);
nice_try.field += 12;
assert_eq!(nice_try.field, 0);
let (mut a, mut b) = <(Unstable, Unstable) as Default>::default();
assert_eq!((a.field, b.field), (0, 0));
std::mem::swap(&mut a.field, &mut b.field);
assert_eq!((a.field, b.field), (10, 10));
}
Vec
. Как это работает? Неужели реализация этих методов скопирована с методов для слайсов? Конечно, нет.Deref<Target = [T]>
". Что это означает? В стандартной библиотеке Rust (на самом деле core) есть трейт Deref, описанный следующим образом:trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
Как нетрудно видеть, он позволяет получить из ссылки на значение одного типа ссылку на значение другого типа. У этого трейта есть дополняющий его DerefMut, который выполняет преобразование между мутабельными ссылками:trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Возникает закономерный вопрос: а чем они отличается от AsRef и AsMut, которые также выполняют преобразования между ссылками? Ну, во-первых, в силу того, что целевой тип преобразования у Deref
и DerefMut
является ассоциированным типом, а не обобщённым параметром, у любого типа может быть не более одной реализации Deref
и DerefMut
. А во-вторых — и это уже куда как более существенное отличие — это один из "магических" (известных компилятору) трейтов, методы которого часто вызываются неявно. Конкретно методы Deref{, Mut}
вызываются для кастомных реализаций операторов разыменовывания, а также ещё неявно в некоторых контекстах, в частности, при вызове методов через точку. Это, в том числе, позволяет эргономично пользоваться умными указателями. Если на минуту забыть о том, что Vec
может менять свою длину, мы можем считать его ссылкой на лежащий в куче массив с длиной, известной лишь на этапе исполнения. То есть... Указатель на слайс. И действительно, Vec
реализует Deref{, Mut}<Target = [T]>
, что позволяет вызывать на нём методы, определённые для слайса. И при этом без каких-либо дополнительных телодвижений с вызывающей стороны!foo.field
и foo
имеет тип Foo
, у которого нету поля field
, но Foo
реализует Deref<Target = Bar>
, где у типа Bar
есть поле field
, то такое обращение к полю будет корректно и будет вызывать deref
(или deref_mut
). Все те странные структуры, которые я показал, так или иначе оборачивают структуру Simple
:#[derive(Default)]
struct Simple {
field: u32,
}
NiceTry
. Вот её определение:#[derive(Default)]У нас есть два поля одного типа. Как же работает трюк с подменой? Очень просто: в
struct NiceTry {
first: Simple,
second: Simple,
}
Deref::deref
мы возвращаем ссылку на одно поле, а в DerefMut::deref_mut
— на другое:impl Deref for NiceTry {
type Target = Simple;
fn deref(&self) -> &Self::Target {
&self.first
}
}
impl DerefMut for NiceTry {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.second
}
}
Неудивительно, что мы не могли поменять field
— мы не могли получить к нему мутабельный доступ!Unstable
устроен несколько сложнее. Если у нас есть мутабельный доступ к полю внутри метода, то мы можем его... Мутировать. То есть поменять. Собственно, именно это и происходит в реализации deref_mut
:#[derive(Default)]
struct Unstable(Simple);
impl Deref for Unstable {
type Target = Simple;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Unstable {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.field += 10;
&mut self.0
}
}
"Ладно" — может сказать кто-то из моих читателей — "я понимаю, как работает трюк с NiceTry и Unstable, но что, чёрт побери, происходит с Tricky? Мы ведь даже не используем мутабельный доступ к Tricky, так что мы не можем что-то там поменять!". Что ж, мой недоумённый читатель (а также другие), я вынужден раскрыть одну из самых грязных тайн Rust: &mut T
на самом деле не означает "изменяемая ссылка"! Что же это тогда? Фактически, &mut T
означает уникальную ссылку на значение типа T
— то есть такую, что в любой момент времени одновременно с этой ссылкой не существует никаких других. Почему это так важно? Дело, что одна из вещей, которая является в Rust неопределённым поведением — это гонка данных, ситуация, когда доступ к одному и тому же значению происходит из двух (или более) разных мест одновременно, и при этом как минимум один доступ — на запись. Простейший (концептуально, разумеется) доступ добиться отсутствия гонок данных — это убедиться, что в любой момент времени только кто-то один имеет изменяемый доступ к данным. Это именно то, что компилятор Rust проверяет, используя концепции владения и заимствования. Но это не единственный способ!&
) ссылкам. И действительно, если мы откроем документацию к, скажем, AtomicU32, то мы увидим, что практически все методы принимают &self
.Tricky
? Какое-то атомарное значение? Нет, field
— это просто u32
, а сделать атомарной саму структуру Simple
мы не можем. RefCell
? Тоже нет. Метод RefCell::borrow
возвращает значение особого типа, которое реализует Deref
в оборачиваемые данные и которое в деструкторе уменьшает счётчик неизменяемых доступов. Но, напомним, в методе deref
нам нужно вернуть именно ссылку. Единственный способ получить получить ссылку на внутреннее значение в RefCell
— это вызвать borrow{, _mut}
, который вернёт тот прокси-тип. Но Rust просто не даст нам вернуть ссылку на локальную переменную! Выходит, этот вариант тоже отпадает. Что же тогда используется в Tricky
?Cell
позволяет доставать (копию) внутреннего значения, переписывать значение, замещать его другим (в том числе и значением по умолчанию) и обмениваться значением с другим Cell
, но не даёт возможности получить разделяемую ссылку на значение внутри — это дало бы возможность передать ссылку в другой поток и организовать чтение из одного потока и запись в другом (кстати, достать &mut
ссылку можно, ибо её уникальность гарантирует компилятор). Вторая причина — и это, на мой взгляд, очень хорошо демонстрирует силу Rust — Cell
не реализует Sync, то есть &Cell<T>
нельзя пересылать из одного потока в другой. Это означает, что в любой момент времени ссылки на Cell
, если таковые есть, все принадлежат одному потоку, то есть коду, который по отношению к Cell
исполняется последовательно. Таким образом, даже не смотря на то, что доступ к Cell
может быть из нескольких мест и каждый доступ может менять значение внутри, последовательность (в смысле "не параллельность") исполнения кода гарантирует, что они доступы не могут быть активны одновременно.Tricky
:struct Tricky {
choice: Cell<Choice>,
first: Simple,
second: Simple,
third: Simple,
}
impl Deref for Tricky {
type Target = Simple;
fn deref(&self) -> &Self::Target {
let choice = self.choice.get();
self.choice.set(choice.next());
match choice {
Choice::First => &self.first,
Choice::Second => &self.second,
Choice::Third => &self.third,
}
}
}
Здесь Choice
— это некоторое перечисление, которое являет собой выбор из трёх вариантов. В deref
код обновляет выбор поля и в соответствии с предыдущим значением choice
выбирает нужное поле.&mut
-ссылку на него, но нельзя через &
-ссылку. Это называется inherited mutability (наследуемая изменяемость), поскольку значение "наследует" изменяемость обёртки. Однако, как мы видели, значение может быть изменяемым и через &
-ссылку. Это называется interiour mutability (внутренняя изменяемость). Это нужная на практике вещь, поскольку Rc/Arc и мьютекс с безопасным интерфейсом без неё создать невозможно.&mut
на &uniq
, убедительно аргументируя, что это куда точнее отражает суть. Это предложение вызвало в своё время бурные обсуждения, но в итоге, как вы видите, так и не было принято. Возможно, и зря.&str
, &&str
, &String
, impl FnMut(char) -> bool
и (почему-то малоизвестный) &[char]
. Таким образом, разбить строку по нескольким символам легко:let result = "Hello, world!".split(&['o', 'l'][..]).collect::<Vec<_>>();
assert_eq!(result, vec!["He", "", "", ", w", "r", "d!"]);
&mut std::str::Chars
, что она может с ним сделать?Chars
, мы можем редактировать произвольным образом, в том числе и поменять его целиком. Chars
внутри себя содержит строки, символы которой он перебирает, и при помощи метода Chars::as_str эту строку можно достать. Таким образом, имея мутабельную ссылку на Chars
, можно вытащить из него строку, вырезать из строки нужный кусок и переписать переданный итератор .chars()
от этого кусочка.Chars
(медленный, требующий аллокаций и не совсем корректный):fn extract_line2(chars: &mut Chars) -> String {
chars.take_while(|&ch| !matches!(ch, '\r' | '\n')).collect()
}
fn extract_line<'s>(chars: &mut Chars<'s>) -> Option<&'s str> {
let s = chars.as_str();
let line = s.lines().next()?;
*chars = s[line.len()..].chars();
Some(line)
}
File.exists
.