Как я могу объединить смежные фрагменты в Rust

У меня в памяти два смежных среза. Я знаю, что они смежные, потому что я только что создал их из одного фрагмента с помощью 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, я хочу объединить уже соседние кусочки на месте без копирования.

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

На самом деле это несколько спорная тема: еще не решено, должна ли такая возможность вообще быть возможной, и текущий консенсус (согласно моей интерпретации дискуссий), похоже, опирается на нее. Из документации в 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, и вызывающий должен гарантировать, что они из одного и того же исходного объекта.

В этом есть смысл. Я понимаю, почему это невозможно безопасно, и я собираюсь использовать полностью безопасный путь кода, который удваивает некоторую работу по синтаксическому анализу, пока у меня не будет доказательств, что мне это действительно нужно. Спасибо за объяснение и пример!

ddulaney 15.04.2023 20:42

Другие вопросы по теме