Можно ли преобразовать Borrow <T>в AsRef <T>или наоборот? - PullRequest
1 голос
/ 04 августа 2020

У меня есть переменная tokens: &[AsRef<str>], и я хочу объединить ее в одну строку:

// tokens: &[AsRef<str>]
let text = tokens.join("") // Error
let text = tokens.iter().map(|x|x.as_ref()).collect::<Vec<_>>().join("") // Ok, but...

Второй вариант неудобен и неэффективен, потому что он перераспределяет элементы в новый Vec.

Согласно исходный код , join можно применить к tokens, если его тип &[Borrow<str>]:

// if tokens: &[Borrow<str>]
let text = tokens.join("") // OK

// so I want to convert &[AsRef<str>] to &[Borrow<str>]
let text = convert_to_borrow(tokens).join("")

Как мне это сделать? Почему Join реализован для типов, реализующих Borrow, но не AsRef?

Ответы [ 2 ]

3 голосов
/ 04 августа 2020

Ответ Trentcl дает вам решение вашей реальной проблемы, полагаясь только на реализацию AsRef<str>. Следующее - это скорее ответ на более общий вопрос из вашего заголовка.

Определенные черты несут с собой инварианты, которые реализации должны обеспечивать. В частности, если реализации Borrow<T> также реализуют Eq, Hash и Ord, то реализации этих трейтов для T должны вести себя идентично. Это требование является способом сказать, что заимствованное значение "то же самое" , что и исходное значение, но просто рассматривается по-другому. Например, реализация String: Borrow<str> должна возвращать весь фрагмент строки; было бы неправильно возвращать подслой.

AsRef не имеет этого ограничения. Реализация AsRef<T> может реализовать такие черты, как Hash и Eq, совершенно иначе, чем T. Если вам нужно вернуть ссылку только на часть структуры, тогда AsRef может это сделать, а Borrow - нет.

Все это означает, что вы не можете получить действительную реализацию Borrow<T> из произвольная реализация AsRef<T>: реализация AsRef может не обеспечивать соблюдение инвариантов, которые требуются Borrow.

Однако работает наоборот. Вы можете создать реализацию AsRef<T> с произвольным Borrow<T>:

use std::borrow::Borrow;
use std::convert::AsRef;
use std::marker::PhantomData;

pub struct BorrowAsRef<'a, T: ?Sized, U: ?Sized>(&'a T, PhantomData<U>);

impl<'a, T, U> AsRef<U> for BorrowAsRef<'a, T, U>
where
    T: Borrow<U> + ?Sized,
    U: ?Sized,
{
    fn as_ref(&self) -> &U {
        self.0.borrow()
    }
}

pub trait ToAsRef<T: ?Sized, U: ?Sized> {
    fn to_as_ref(&self) -> BorrowAsRef<'_, T, U>;
}

impl<T, U> ToAsRef<T, U> for T
where
    T: ?Sized + Borrow<U>,
    U: ?Sized,
{
    fn to_as_ref(&self) -> BorrowAsRef<'_, T, U> {
        BorrowAsRef(self, PhantomData)
    }
}
fn borrowed(v: &impl Borrow<str>) {
    needs_as_ref(&v.to_as_ref())
}

fn needs_as_ref(v: &impl AsRef<str>) {
    println!("as_ref: {:?}", v.as_ref())
}

Почему Join реализован для типов, реализующих Borrow, но не AsRef?

Это общая реализация для всех типов, которые реализовать Borrow<str>, что означает, что он также не может быть реализован для типов, реализующих AsRef<str>. Даже с включенной нестабильной функцией min_specialization это не сработает, потому что реализация AsRef не более "специфическая c", чем реализация Borrow. Поэтому им пришлось выбрать один или другой.

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

3 голосов
/ 04 августа 2020

Это может быть немного медленнее, но вы можете collect итератор &str s непосредственно в String.

let text: String = tokens.iter().map(|s| s.as_ref()).collect();

Это возможно, потому что String реализует FromIterator<&'_ str>. Этот метод увеличивает String путем многократного вызова push_str, что может означать, что его нужно перераспределять несколько раз, но он не создает промежуточный Vec<&str>. В зависимости от размера используемых фрагментов и строк это может быть медленнее (хотя в некоторых случаях также может быть немного быстрее). Если разница будет значительна для вас, вы должны протестировать обе версии.

Невозможно рассматривать фрагмент T: AsRef<str>, как если бы он был фрагментом T: Borrow<str>, потому что не все, что реализует AsRef реализует Borrow, поэтому в обобщенном коде c компилятор не может знать, какую реализацию Borrow применить.

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