Абстрагирование индексов в rust

Мне нужна черта, которая является индексируемой. Однако его реализация может быть «стандартным» массивом (тот, который фактически содержит то, что он возвращает; например, Vec целых чисел) или нестандартным массивом, который создает значение в индексной функции (и поэтому должен возвращать значение, а не ссылку). ). Как это сделать проще всего? Я не могу использовать трейт Индекс, так как он не позволит последнее. В настоящее время похоже, что мне придется обернуть «стандартные индексы» (например, Vec) пользовательской чертой индекса, которая возвращает некоторый Self::Output (а не &Self::Output). Звучит слишком много для такой простой абстракции, которая, я ожидаю, будет довольно распространенной потребностью.

(О времени жизни; будь то значение или ссылка, я намерен использовать индексированное значение только во время жизни индексатора)

Вам действительно нужна нотация []? Если нет, вы можете изобрести свой собственный трейт Indexable с помощью метода .at(position), реализовать его для любого типа, реализующего стандартный трейт Index (используя разыменование), а затем реализовать его совершенно другим способом для типов, которые должны генерировать результат вместо доступа к нему.

prog-fh 15.07.2023 12:15
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
0
1
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Операция v[i] разрешается как *v.index(i); метод .index() происходит от признака std::ops::Index. Этот признак std::ops::Index нельзя напрямую повторно использовать в вашем конкретном случае для возврата значения, поскольку это изменит его семантику.

Если нам абсолютно не нужна запись [], то мы можем определить свой собственный признак Indexable, предназначенный для возврата значения по заданному индексу.

Все типы, которые уже реализуют std::ops::Index, могут автоматически реализовывать этот новый признак Indexable, клонируя элемент, на который ссылаются, чтобы предоставить значение (а не ссылку). Конечно, это относится только к контейнерам, элементы которых реализуют Clone.

Любой конкретный тип, соответствующий вашему варианту использования, может реализовать Indexable по-своему.

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

Пожалуйста, найдите ниже пример для всего этого.

/// A specific trait to obtain a _value_ at a given index.
trait Indexable<Idx>
where
    Idx: ?Sized,
{
    type Output: ?Sized;
    fn value_at(
        &self,
        idx: Idx,
    ) -> Self::Output;
}

/// Generic implementation of Indexable for anything that implements Index.
///
/// The stored values must be clone-able in order to provide a value
/// without consuming the container.
impl<T: ?Sized, Idx, V> Indexable<Idx> for T
where
    T: std::ops::Index<Idx, Output = V>,
    V: Clone,
{
    type Output = V;

    fn value_at(
        &self,
        idx: Idx,
    ) -> Self::Output {
        self.index(idx).clone()
    }
}

/// A specific type for the purpose of the example
struct Dummy {}

/// This implementation of Indexable for this specific type
/// produces a value instead of accessing a previously stored one.
impl Indexable<usize> for Dummy {
    type Output = f64;

    fn value_at(
        &self,
        idx: usize,
    ) -> Self::Output {
        idx as f64 * 0.1
    }
}

fn work_with_indexable<Src: Indexable<usize, Output = f64>>(
    source: &Src,
    range: std::ops::Range<usize>,
) -> f64 {
    let mut accum = 0.0;
    for i in range {
        let v = source.value_at(i);
        println!("got {} at {}", v, i);
        accum += v;
    }
    accum
}

fn main() {
    println!("~~~~ generic implementation used on a vector ~~~~");
    let v = vec!["aa".to_owned(), "bb".to_owned(), "cc".to_owned()];
    for i in 0..v.len() {
        println!("vector at {} ~~> {}", i, v.value_at(i));
    }
    println!("~~~~ generic implementation used on an array ~~~~");
    let a = ["dd".to_owned(), "ee".to_owned(), "ff".to_owned()];
    for i in 0..a.len() {
        println!("array at {} ~~> {}", i, a.value_at(i));
    }
    println!("~~~~ specific implementation used on a dedicated type ~~~~");
    let d = Dummy {};
    for i in 0..3 {
        println!("dummy at {} ~~> {}", i, d.value_at(i));
    }
    println!("~~~~ using different implementations ~~~~");
    let r1 = work_with_indexable(&[1.2, 2.3, 3.4], 0..3);
    println!("slice: {}", r1);
    let r2 = work_with_indexable(&d, 0..3);
    println!("dummy: {}", r2);
}
/*
~~~~ generic implementation used on a vector ~~~~
vector at 0 ~~> aa
vector at 1 ~~> bb
vector at 2 ~~> cc
~~~~ generic implementation used on an array ~~~~
array at 0 ~~> dd
array at 1 ~~> ee
array at 2 ~~> ff
~~~~ specific implementation used on a dedicated type ~~~~
dummy at 0 ~~> 0
dummy at 1 ~~> 0.1
dummy at 2 ~~> 0.2
~~~~ using different implementations ~~~~
got 1.2 at 0
got 2.3 at 1
got 3.4 at 2
slice: 6.9
got 0 at 0
got 0.1 at 1
got 0.2 at 2
dummy: 0.30000000000000004
*/

Плохая идея определять Indexable таким образом и всегда клонировать. Лучше определить его как GAT.

Chayim Friedman 15.07.2023 21:38

@ChayimFriedman Извините, я не знаю, как это сделать. Возможно, вам следует опубликовать ответ, который показывает лучший способ справиться с этой ситуацией. Мне было бы интересно посмотреть, как это должно быть сделано; Спасибо.

prog-fh 15.07.2023 21:43

Я попытался построить пример, но мне не удалось преодолеть ограничение времени жизни. Я думаю, что это невозможно определить как GAT.

Chayim Friedman 15.07.2023 21:46

@ prog-fh Я попробовал. Дайте мне знать, если я что-то упустил или упустил из виду!

BrownieInMotion 16.07.2023 02:13
Ответ принят как подходящий

Отработав ответ, предоставленный prog-fh, вот моя попытка написать Indexable, избегая клонирования. Это позволяет нам напрямую возвращать ссылки при реализации Index, а также писать реализации, которые возвращают значения, не являющиеся ссылками.

trait Indexable<'a, Idx>
where
    Idx: ?Sized,
{
    type Output<'b>: ?Sized
    where
        Self: 'b,
        'a: 'b;
    fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
    where
        'a: 'b;
}

impl<'a, T: ?Sized, Idx, V> Indexable<'a, Idx> for T
where
    T: std::ops::Index<Idx, Output = V>,
    V: 'a,
{
    type Output<'b> = &'b V where Self: 'b, 'a: 'b;
    fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
    where
        'a: 'b,
    {
        self.index(idx)
    }
}

struct Custom {}

impl Indexable<'static, usize> for Custom {
    type Output<'a> = usize;

    fn at<'a>(&'a self, idx: usize) -> Self::Output<'a>
    where
        'static: 'a,
    {
        idx * 2
    }
}

fn main() {
    println!("testing on vector");
    let v = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
    for i in 0..v.len() {
        println!("{} -> {}", i, *v.at(i));
    }

    println!("testing on custom");
    let c = Custom {};
    for i in 0..3 {
        println!("{} -> {}", i, c.at(i));
    }
}

Просто вопрос: когда я использую его в другом свойстве (например, при возврате «индексируемого» из метода), что мне делать со временем жизни? Есть ли способ, с помощью которого я смогу не делать саму новую черту общей?

Bipolo TheGod 16.07.2023 09:58

@BipoloTheGod, ваше замечание здесь, является основной проблемой этого подхода: семантика меняется от одной реализации к другой. Если некоторые реализации возвращают значения, а другие реализации возвращают ссылки, как мы можем использовать их все в общем контексте? Алгоритм работы со значениями будет отличаться от алгоритма работы со ссылками.

prog-fh 16.07.2023 10:10

@prog-fh Ну, действительно, в сборке все будет иначе, но теоретически это может быть деталь реализации, которая не представлена ​​в самом признаке (легко представить с помощью С++)

Bipolo TheGod 16.07.2023 13:45

@BipoloTheGod В C++ нет различий в синтаксисе при использовании ссылки или значения (знак * неявно присутствует в C++, но это деталь, специфичная для этого специфического языка; я не знаю другого подобного языка, многие другие используйте ссылки везде, так что даже не нужно делать различие). В C или в Rust есть разница между использованием или неиспользованием операции разыменования (*), потому что в конце концов это принципиально отличается, и даже больше в Rust, где при использовании ссылок в игру вступают соображения времени жизни.

prog-fh 16.07.2023 15:07

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