У меня в памяти два смежных среза. Я знаю, что они смежные, потому что я только что создал их из одного фрагмента с помощью split_at
. Я хочу объединить их вместе, чтобы снова получить фрагмент, охватывающий весь диапазон. Есть ли способ сделать это безопасно? В идеале, некоторая функция, которая берет несколько срезов, проверяет, соседствуют ли они друг с другом, и, если возможно, объединяет их вместе.
fn main() {
let original_slice: &[u8] = &[1, 2, 3, 4];
let (left, right) = original_slice.split_at(2);
let rejoined_slice = todo!("take left and right and return an Option or similar")
assert_eq!(original_slice, rejoined_slice);
assert_eq!(original_slice.as_ptr(), rejoined_slice.as_ptr());
}
Это подходит для меня, используя ящик zerocopy
, чтобы выполнить синтаксический анализ с нулевым копированием. Иногда я получаю несколько фрагментов одного типа, которые, как я знаю, находятся рядом друг с другом (в конце концов, я только что проанализировал их, проверил их выравнивание и т. д.), и было бы полезно объединить их вместе.
Не дубликат этого вопроса: я не хочу получить новый Vec
, я хочу объединить уже соседние кусочки на месте без копирования.
На самом деле это несколько спорная тема: еще не решено, должна ли такая возможность вообще быть возможной, и текущий консенсус (согласно моей интерпретации дискуссий), похоже, опирается на нее. Из документации в std::ptr:
Происхождение
Этот раздел не является нормативным и является частью эксперимента Strict Provenance.
Указатели — это не просто «целое число» или «адрес». Например, бесспорно можно сказать, что использование после освобождения явно является неопределенным поведением, даже если вам «повезет», и освобожденная память будет перераспределена до того, как вы начнете чтение/запись (на самом деле это наихудший сценарий, UAF будет намного проще). меньше беспокойства, если бы этого не произошло!). Чтобы обосновать это утверждение, указатели должны каким-то образом быть чем-то большим, чем просто их адреса: они должны иметь происхождение.
...
Уменьшение происхождения невозможно отменить: даже если вы «знаете», что существует большее выделение, вы не можете получить указатель с большим происхождением. Точно так же вы не можете «рекомбинировать» два смежных происхождения обратно в одно (то есть с помощью
fn merge(&[T], &[T]) -> &[T]
).
Если в конечном итоге будет решено, что расширение происхождения разрешено, вы можете сделать это с помощью нескольких операций с указателями:
/// SAFETY: Both parameters must refer to the same allocated object.
unsafe fn rejoin<'a, T>(a: &'a [T], b: &'a [T]) -> Option<&'a [T]> {
if a.as_ptr().add(a.len()) == b.as_ptr() {
Some(std::slice::from_raw_parts(a.as_ptr(), a.len() + b.len()))
} else {
None
}
}
Но обратите внимание, что я все же сделал функцию unsafe
и оставил комментарий, подтверждающий это. Теоретически два среза могут быть смежными, но при этом быть отдельными объектами. Например, let a = [1, 2];
и let b = [3, 4];
могут разумно быть непрерывными переменными в стеке, но обращение к ним как к одному срезу было бы неопределенным поведением. Из-за этого функция должна быть unsafe
, и вызывающий должен гарантировать, что они из одного и того же исходного объекта.
В этом есть смысл. Я понимаю, почему это невозможно безопасно, и я собираюсь использовать полностью безопасный путь кода, который удваивает некоторую работу по синтаксическому анализу, пока у меня не будет доказательств, что мне это действительно нужно. Спасибо за объяснение и пример!