Исследуя, как перезаписать часть фрагмента байтов ([u8]
), я заметил следующую реализацию Write::write
в стандартной библиотеке Rust. (См. также исходники на GitHub.)
#[stable(feature = "rust1", since = "1.0.0")]
impl Write for &mut [u8] {
#[inline]
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
let amt = cmp::min(data.len(), self.len());
let (a, b) = mem::take(self).split_at_mut(amt);
a.copy_from_slice(&data[..amt]);
*self = b;
Ok(amt)
}
...
}
Согласно документации, эта функция должна записывать заданный data
в self
. Однако я не понимаю, как работает эта функция.
let (a, b) = mem::take(self).split_at_mut(amt);
Насколько я понимаю, mem::take
должен вернуть копию содержимого self
. Он также должен заменить содержимое self
пустым фрагментом, что бы это ни значило, но сейчас это не актуально. После этого эта копия разбивается на две части: a
и b
.
a.copy_from_slice(&data[..amt]);
Это единственная строка, где мы действительно используем данный data
. Однако мы копируем его только в a
, который сам по себе является лишь частью копии. После этого a
больше никогда не используется. Как содержимое data
попадает в self
?
*self = b;
Я также не уверен, что делает этот последний оператор, но, похоже, он ничего не делает с нашими входящими данными. Если бы я проигнорировал документацию и имя функции, я бы интуитивно ожидал от реализации следующего результата:
fn main() {
use std::io::Write;
let mut buffer = String::from("abcdefg");
let _ = unsafe { buffer.as_bytes_mut().write(b"xyz") };
println!("{:?}", buffer);
// Intuitive expectation: "defdefg"
// Actual result: "xyzdefg"
}
Фактический результат соответствует тому, что я ожидал на основе имени функции и документации, но не на основе реализации функции. Что мне не хватает?
@cafce25 После просмотра вашего комментария я думаю, что отчасти мое замешательство вызвано тем, что &mut [u8]
— это не изменяемая ссылка на [u8]
, а другая конструкция, отличная от &mut
перед другими типами. Другими словами, &mut [u8]
— это весь кусочек, а не только [u8]
. Я знал, что &mut
перед [u8]
делает изменяемым содержимое среза, а не сам срез, что, я думаю, привело меня к бессознательной предвзятости, согласно которой вы не можете иметь (изменяемые) ссылки на срез, что явно неверно. (Возможно, это также вызвало бы различные противоречия с остальными моими знаниями.)
Это немного неправильно: [u8]
— это срез, но обычно мы говорим срез, когда на самом деле имеем в виду ссылку на срез (& [u8]
), потому что срезы нуждаются в косвенности. Итак, &mut [u8]
на самом деле является изменяемой ссылкой на фрагмент байтов [u8]
.
@cafce25 Хорошо, это имеет смысл. Ссылки на срезы по-прежнему отличаются от «обычных» ссылок. И смысл среза закодирован в этих специальных ссылках. В любом случае, спасибо, что указали на неправильную номенклатуру. То, что срезы являются не ссылками, а областью памяти, на которую указывают, имеет абсолютный смысл. Если посмотреть на это с этой точки зрения, синтаксис также станет более понятным. :D
Важно помнить, что срез в Rust не содержит никаких данных, он ссылается на данные. Срез — это просто указатель на начало некоторых данных и длину этих данных.
Во-вторых, метод write
для срезов также «обновляет срез, чтобы он указывал на еще не написанную часть»:
fn main() {
use std::io::Write;
let mut buffer = String::from("abcdefg");
let mut bytes = unsafe { buffer.as_bytes_mut() };
let _ = bytes.write(b"xyz");
println!("{:?}", bytes); // b"defg"
}
Вот что делает *self = b
. b
— это незаписанная часть среза, и мы присваиваем ее *self
. Для этого нам нужно mem::take
:
Без исключения времени жизни метод выглядит так:
impl<'slice> Write for &'slice mut [u8] {
#[inline]
fn write<'this>(&'this mut self, data: &[u8]) -> io::Result<usize> {
let amt = cmp::min(data.len(), self.len());
let (a, b) = mem::take(self).split_at_mut(amt);
a.copy_from_slice(&data[..amt]);
*self = b;
Ok(amt)
}
...
}
Таким образом, тип Self
равен &'slice mut [u8]
(потому что это тема блока impl), что означает, что тип &'this mut Self
(т. е. тип self
) равен &'this mut &'slice mut [u8]
. Это означает, что для присвоения *self
нам нужны данные, которые переживут 'slice
, внешнее время жизни. Но поскольку self
стоит за ссылкой, которая переживёт только 'this
, мы можем получить из неё только разыменование на данные, которые переживут 'this
. А поскольку split_at_mut
имеет подпись split_at_mut<'a>(&'a mut self) -> (&'a mut T, &'a mut T)
, он даст нам только фрагменты, которые сохраняются 'this
при вызове self
(поскольку время жизни двух подсрезов привязано к времени жизни входных данных).
Нам нужно извлечь &'slice mut [u8]
из &'this mut &'slice mut [u8]
, и именно это и делает mem::take
. Он имеет (упрощенную) подпись take<T>(&mut T) -> T
, в нашем случае take<&'slice mut [u8]>(&mut &'slice mut [u8]) -> &'slice mut [u8]
. Он делает это, оставляя вместо него замену по умолчанию (в данном случае пустой фрагмент, который представляет собой просто указатель, который может или не может висеть, и длину 0).
Теперь a
и b
являются справочными данными, которые сохраняются после 'slice
, и мы можем свободно присваивать b
*self
.
Правильно ли я вас понимаю, что mem::take
фактически копирует только ссылку (т. е. фрагмент), но не контент? И в то же время, *self = b
тоже только перезаписывает фрагмент? Я знаю, что срезы являются ссылками, но моя ошибка заключалась в том, что я не осознавал и не учитывал, что у вас может быть (изменяемая) ссылка на срез (которая сама по себе является ссылкой). Но, конечно, можно, спасибо за ответ.
По сути, да. Байты, на которые ссылаются срезы, не перемещаются ни на каком этапе всей этой процедуры. take
просто перемещает фрагмент из изменяемой ссылки, за которой он находится.
Обратите внимание, что
Self
— это&mut [u8]
, поэтому получательself
имеет тип&mut &mut [u8]
(двойная изменяемая ссылка).