Как использовать SmallVec с Cow - PullRequest
4 голосов
/ 03 октября 2019

Я хочу использовать SmallVec с Cow. Я попробовал это:

use smallvec::SmallVec;
use std::borrow::Cow;

fn main() {
    let s = "hello world".to_owned();
    let mut s = Cow::Borrowed(s.as_bytes());
    clear_subslice(&mut s, 2, 6);
}

fn clear_subslice(text: &mut Cow<'_, [u8]>, start: usize, end: usize) {
    match text {
        Cow::Borrowed(v) => {
            if !v[start..end].iter().all(|&c| c == b' ') {
                let mut v = SmallVec::from_slice(v);
                v[start..end].iter_mut().for_each(|c| *c = b' ');
                *text = Cow::Owned(v);
            }
        }
        Cow::Owned(v) => {
            v[start..end].iter_mut().for_each(|c| *c = b' ');
        }
    }
}
error[E0271]: type mismatch resolving `<[u8] as std::borrow::ToOwned>::Owned == smallvec::SmallVec<_>`
  --> src/main.rs:16:25
   |
16 |                 *text = Cow::Owned(v);
   |                         ^^^^^^^^^^^^^ expected struct `std::vec::Vec`, found struct `smallvec::SmallVec`
   |
   = note: expected type `std::vec::Vec<u8>`
              found type `smallvec::SmallVec<_>`

Это работает только с типами, которые ToOwned реализованы для определенного типа. В этом случае &[u8] имеет ToOwned, реализованный с целью Vec.

Я пытался реализовать ToOwned с целью как SmallVec, но безуспешно.

Возможно ли этоиспользовать SmallVec с Cow?

Одно из известных мне решений - использовать пользовательское перечисление Cow:

pub enum SmallCow<'a, A: Array> {
    Borrowed(&'a [A::Item]),
    Owned(SmallVec<A>),
}

Есть ли другой способ?

1 Ответ

1 голос
/ 08 октября 2019

В том-то и дело, что Cow<'a, T> требует T для реализации ToOwned, а владельцем версии Cow<'a, T> является связанный тип Owned из ToOwned. Более того, Owned, должен реализовать Borrow<T>. В его нынешнем виде Cow<'a, [u8]> может только использовать Vec<u8> в качестве собственного варианта, поскольку [T] реализует ToOwned с Vec<T> в качестве Owned связанного типа.

Iувидеть два варианта для вас. Либо вы можете сделать свою собственную реализацию Cow, которая использует различные границы признаков (или, как вы предложили, просто специализироваться на вашем конкретном случае использования), либо вы можете использовать новые типы для переноса [u8] и SmallVec<A> и реализации ToOwned на обертке для [u8] и Borrow<SliceWrapper<u8>> на обертке на SmallVec<A>. Я сконцентрируюсь на последнем, поскольку вы, кажется, уже охватили первое.

Новый тип - это оболочка, которая, по сути, объявляет новый тип, который эквивалентен исходному типу, но не имеетлюбые черты или методы. Обычный способ сделать это - использовать структуру кортежа.

use small_vec::{Array, SmallVec};

struct SmallVecWrap<A: Array>(SmallVec<A>);

struct SliceWrap<T>([T]);

Обратите внимание, что SliceWrap<T> - это тип без размера, поскольку [T], поэтому мы всегда будем использовать его за указателем. Это важно, потому что когда мы реализуем Borrow на SmallVecWrap<A>, это будет Borrow<SliceWrap<T>>, а не Borrow<&SliceWrap<T>>. То есть Borrow использует в качестве параметра типа нестандартный тип (я полагаю, что может обойтись без этого, но у вас будет дополнительный уровень косвенности, и вы не сможетеиспользуйте методы мутации на срезе).

Одна из основных проблем, с которой я столкнулся при таком подходе, заключается в том, что, похоже, нет способа превратить &[u8] в &SliceWrap<u8> без небезопасного блока. Это имеет определенный смысл, поскольку без какой-либо дополнительной информации эти два типа могут быть семантически различными. Например, NonZeroU8 находится в аналогичной ситуации, но нет смысла конвертировать u8 в NonZeroU8 без проверки, равен ли он нулю. RFC # 1909, несвязанные значения, может помочь с этим, но я не мог заставить его работать. Я отмечу, что MIRI не обнаружил никаких проблем при запуске на вашем тестовом примере.

Другая проблема с этим подходом состоит в том, что вы должны либо всегда откладывать использование упакованного типа (например, v.0 в примерекод), а затем потенциально обернуть возвращаемое значение или переопределить все черты и методы, которые вам нужны. Эта же проблема относится к подходу SmallCow<'a, A>, но вам нужно только реализовать черты и методы Cow<'a, T>, а их не так много.

Если вы решите всегда откладывать наметоды упакованного типа, вы, вероятно, захотите сделать поля новых типов открытыми (например, SliceWrap<T>(pub [T])), чтобы вы могли использовать их вне этого одного модуля.

Последняя проблема с этим подходом снова связана с ToOwned. Для ToOwned требуется преобразовать один тип, но SmallVecWrap<A> не является единственным типом, даже если тип элементов A фиксирован. Например, &[u8] может быть действительно преобразован в SmallVecWrap<[u8, 1]>, SmallVecWrap<[u8, 2]> и т. Д. Один из возможных обходных путей - присоединить тип A к SliceWrap<T>:

struct SliceWrap<T, A: Array> {
    array: std::marker::PhantomData<A>,
    slice: [T],
}

Затем можно реализоватьToOwned для SliceWrap<T, A> с Owned как SmallVecWrap<A>.

В любом случае, вот полный пример.

use smallvec::{Array, SmallVec}; // 0.6.10
use std::borrow::{Borrow, Cow, ToOwned};

struct SmallVecWrap<A: Array>(SmallVec<A>);

#[repr(transparent)]
struct SliceWrap<T>([T]);

impl<T> SliceWrap<T> {
    // for convenience
    fn from_slice(slice: &[T]) -> &Self {
        // As far as I can tell, there's no way to do this without unsafe.
        // This should be safe since SliceWrap<T> is transparently a [T].
        // All we're doing is changing a (fat) pointer to a [T]
        // into a (fat) pointer to SliceWrap<T>.
        // I won't claim expertise on this, though.
        unsafe { &*((slice as *const [T]) as *const SliceWrap<T>) }
        //          ^                   ^
        // These parentheses aren't needed, but it's clearer this way
    }

    // I guess we didn't need this
    #[allow(dead_code)]
    fn from_mut_slice(slice: &mut [T]) -> &mut Self {
        // Same caveats apply
        unsafe { &mut *((slice as *mut [T]) as *mut SliceWrap<T>) }
    }
}

impl<A: Array> Borrow<SliceWrap<A::Item>> for SmallVecWrap<A> {
    fn borrow(&self) -> &SliceWrap<A::Item> {
        SliceWrap::from_slice(self.0.borrow())
    }
}

// Note: We have to choose a particular array size
// to use for the owned SmallVec<A>.
const OWNED_ARRAY_SIZE: usize = 4;
impl<T: Clone> ToOwned for SliceWrap<T> {
    type Owned = SmallVecWrap<[T; OWNED_ARRAY_SIZE]>;

    fn to_owned(&self) -> SmallVecWrap<[T; OWNED_ARRAY_SIZE]> {
        SmallVecWrap(self.0.into())
    }
}

fn main() {
    let s = "hello world".to_owned();
    let mut s = Cow::Borrowed(SliceWrap::from_slice(s.as_bytes()));
    clear_subslice(&mut s, 2, 6);
}

fn clear_subslice(text: &mut Cow<'_, SliceWrap<u8>>, start: usize, end: usize) {
    match text {
        Cow::Borrowed(v) => {
            if !v.0[start..end].iter().all(|&c| c == b' ') {
                let mut v = SmallVec::from_slice(&v.0);
                v[start..end].iter_mut().for_each(|c| *c = b' ');
                *text = Cow::Owned(SmallVecWrap(v));
            }
        }
        Cow::Owned(v) => {
            v.0[start..end].iter_mut().for_each(|c| *c = b' ');
        }
    }
}

(детская площадка)


Для вас существует третий вариант: не используйте SmallVec<A>, если только вы не определили и не определили, что эти небольшие ассигнования значительно замедляют вашу программу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...